Skip to content

feat(orchestrator): Stage enum + runtime_profile.stages config (slice 1 of #519)#538

Merged
shaun0927 merged 4 commits into
Q00:mainfrom
shaun0927:feat/519-1-stage-enum-config
May 5, 2026
Merged

feat(orchestrator): Stage enum + runtime_profile.stages config (slice 1 of #519)#538
shaun0927 merged 4 commits into
Q00:mainfrom
shaun0927:feat/519-1-stage-enum-config

Conversation

@shaun0927
Copy link
Copy Markdown
Collaborator

Summary

First slice of #519 (Stage-Runtime mapping). Adds the per-stage runtime selection primitive the Agent OS diagram agreed in #476 calls for: each pipeline stage (interview / execute / evaluate / reflect) can be served by a different harness.

This PR ships only the binding-table primitive — the Stage enum, the RuntimeProfileConfig model, and the resolution helper. Subsequent slices wire mcp doctor extensions (5.2/5.3), the per-runtime mappings (5.4–5.7), and docs/acceptance (5.8).

Independent track. This PR does not depend on Sprint 1/2/3/4 work. It branches off main and lands on its own track.

Decisions captured (from the #519 sub-thread sign-off)

Subject Decision
Stage vocabulary Closed: interview, execute, evaluate, reflect. Adding a member requires an explicit justified PR.
Resolution order stages[stage]defaultfallback (orchestrator.runtime_backend)
runtime_profile=None Byte-for-byte today's behaviour (BC commitment from PR #505)
Typo handling Unknown stage keys raise pydantic.ValidationError at startup, surfacing the typo + valid set. Mid-workflow surprises ruled out.

Changes

  • src/ouroboros/orchestrator/stage.py (new) — Stage enum, parse_stage, resolve_runtime_for_stage, UnknownStageError, VALID_STAGE_KEYS.
  • src/ouroboros/config/models.pyRuntimeProfileConfig Pydantic model with field validator; runtime_profile: RuntimeProfileConfig | None = None on OrchestratorConfig.
  • src/ouroboros/config/__init__.py — re-export RuntimeProfileConfig.
  • tests/unit/orchestrator/test_stage_resolution.py — 13 cases.

Verification

Check Result
uv run ruff check clean (after one --fix iteration)
uv run ruff format 2 files reformatted, no logic change
uv run pytest tests/unit/orchestrator/test_stage_resolution.py 13 passed
uv run pytest tests/unit/config/ (regression) 145 passed

Pre-merge checklist

  • Stage enum with exactly 4 members
  • parse_stage rejects unknown values with helpful message
  • resolve_runtime_for_stage picks stages → default → fallback in order
  • runtime_profile=None preserves byte-for-byte today's behaviour
  • Typo in stages key rejected at config validation
  • OrchestratorConfig legacy construction unchanged
  • CI: ruff + format + 13 unit tests + 145 config regression all green

Post-merge checklist

  • Slice 2: extend ouroboros mcp doctor with Medium-tier validation that prints the resolved stage→runtime table and rejects configs whose runtimes are not on $PATH
  • Slice 3: --probe flag for Deep validation (per-runtime ping smoke)
  • Slices 4–7: per-runtime mappings (OpenCode, Hermes, Claude Code, LiteLLM)
  • Slice 8: docs + mixed-stage acceptance test (closes Stage-Runtime mapping — runtime_profile.{interview,execute,evaluate,reflect} #519)

Rollback

Pure additive: a new module, a new config block, two new re-exports. runtime_profile=None default means existing installs see no behaviour change. Rollback removes the kwarg + helpers; legacy code path is unchanged.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Reviewing commit 5addcf2 for PR #538

Review record: 5b91d748-80df-46e9-b926-a7e202a87c34

Blocking Findings

| # | File:Line | Severity | Finding |
|### Recovery Notes
First recoverable review artifact generated from codex analysis log.

---|-----------|----------|---------|
| 1 | src/ouroboros/config/models.py:298 | BLOCKING | RuntimeProfileConfig.default and the stages values are unconstrained strs, so invalid backends like "cluade" or empty strings pass startup validation even though this feature is supposed to fail bad routing config early. In practice that means a malformed runtime_profile loads successfully and only fails later when some stage is dispatched, or silently falls through because resolve_runtime_for_stage() treats falsy strings as “unset”. These fields need the same backend validation/normalization as orchestrator.runtime_backend, and the tests should cover bad backend values instead of only bad stage keys. |

Non-blocking Suggestions

None.

Design Notes

The split between a closed Stage enum and a small resolver is a sensible primitive for later wiring. The weak spot is the new config contract: stage names are validated, but backend names are not, so the abstraction currently exposes an unsafe configuration surface.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

@shaun0927
Copy link
Copy Markdown
Collaborator Author

Follow-up architectural decisions for #519 slices 2–8

The post-merge checklist on this PR lists the follow-up slices but does not enumerate the architectural decisions each slice depends on. Spelling those out here so reviewers and the next contributor see the open questions explicitly.

This PR (slice 1) is intentionally non-controversial — Stage enum + config schema + resolution helper. The decisions below land in subsequent slices, not in this PR.


Slice 2 — ouroboros mcp doctor Medium validation

Goal. ouroboros mcp doctor validates the configured runtime_profile.stages block at startup time and prints a resolved stage→runtime table.

Open decisions:

  1. Validation depth. Per the Stage-Runtime mapping — runtime_profile.{interview,execute,evaluate,reflect} #519 sub-thread we agreed Medium (binary on $PATH + name in registry). The implementation:

    • Option A: Re-use the existing mcp doctor checks for each runtime independently (loops over stages keys).
    • Option B: Add a generic validate_runtime_for_stage(stage, runtime_id) helper that each runtime adapter implements via a Protocol.
    • Recommendation: A first, B as a refactor when adding the second per-runtime check.
  2. Failure mode. When validation fails, does mcp doctor exit non-zero (CI-friendly), warn (operator-friendly), or both via --strict?

    • Recommendation: both. Default warns; --strict exits non-zero. Matches the existing flag pattern.
  3. Table format. Print as plain text, JSON, or both via --json? Affects shell-script consumers (e.g. CI checking the resolved stages).

    • Recommendation: plain by default, --format=json opt-in.

Dependencies. Slice 1 (this PR) — landed.


Slice 3 — --probe Deep validation

Goal. A new ouroboros mcp doctor --probe flag issues a "ping"-style RPC to each runtime and reports per-stage round-trip time.

Open decisions:

  1. Probe shape. What does "ping" mean per runtime?

    • Option A: Each runtime adapter exposes a probe() method returning (ok: bool, latency_ms: int, version: str).
    • Option B: Run a tiny complete() call with a fixed prompt and assert success.
    • Recommendation: A. Cheaper, no token spend, and exposes runtime version metadata.
  2. Timeout policy. What's the per-runtime timeout?

    • Recommendation: 5s default, --probe-timeout=N override.
  3. Network considerations. Probe touches external services for some runtimes (Anthropic, OpenAI). Default behaviour: do we probe by default in --probe mode, or require explicit --probe-network?

    • Recommendation: probe network in --probe mode (it's an opt-in flag); never in default mode.

Dependencies. Slice 2 (mcp doctor table-print extension).


Slices 4–7 — Per-runtime stage mappings (OpenCode / Hermes / Claude Code / LiteLLM)

Goal. Each runtime gets a per-stage profile so runtime_profile.stages.evaluate = "claude_code" actually wires Claude Code through evaluate. Mirrors what PR #505 (open) does for Codex.

Open decisions (apply to all four slices):

  1. Mapping pattern. PR feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488) #505 introduces worker → codex exec --profile ouroboros-worker. Each follow-up slice picks an analogous mapping. Open: do all runtimes use ouroboros-<stage> profile naming, or do we let each runtime pick its convention?

    • Recommendation: align on ouroboros-<stage>. Operator predictability > per-runtime ergonomics.
  2. Mapping location. Where does the per-runtime profile live in code?

    • Option A: Inside each runtime's adapter file (e.g. providers/opencode_adapter.py gets a STAGE_PROFILES = {...} constant).
    • Option B: Centralised in orchestrator/stage.py next to the resolution helper.
    • Recommendation: A. Each runtime's profile is its own concern; central registry would couple unrelated providers.
  3. Validation hook. When mcp doctor validates a stage→runtime mapping, does it ask the runtime to confirm the profile exists?

    • Recommendation: yes, via the validate_runtime_for_stage Protocol from slice 2.

Dependencies.


Slice 8 — Docs + mixed-stage acceptance test (closes #519)

Goal. End-to-end test fixing the contract: a fixture run with interview→codex / execute→opencode / evaluate→claude_code / reflect→hermes produces an EventStore where each stage's runtime_id matches the configuration. Plus user-facing docs.

Open decisions:

  1. Where do per-stage runtime_id fields land? Two natural homes:

    • Option A: control.directive.emitted event with target_type=execution, extra={"runtime_id": ...} at each stage transition.
    • Option B: A new lineage.stage.transition event category.
    • Recommendation: A. Reuses the existing event category and the extra slot's narrow-membership rule (feat(events): add control.directive.emitted event factory #492).
  2. Acceptance assertion. What does the test assert?

    • Recommendation: per-stage control.directive.emitted events exist with extra.runtime_id matching the configured value, and the resolution helper's output for each stage matches extra.runtime_id.
  3. Docs scope. What user-facing surface is documented?

    • Option A: A new docs/guides/runtime-profile-stages.md.
    • Option B: Extend docs/guides/mcp-bridge.md (the existing per-runtime guide).
    • Recommendation: A. Cleaner separation; the bridge guide is for MCP, runtime profile is orthogonal.

Dependencies. Slices 1–7 + PR #505.


Decision summary table

Slice Hard dependency Open architectural decisions
2 (mcp doctor Medium) Slice 1 (1) per-runtime check pattern, (2) failure mode, (3) output format
3 (--probe Deep) Slice 2 (1) probe shape, (2) timeout, (3) network probe gating
4–7 (per-runtime mappings) Slice 1 + #505 merged (1) profile naming convention, (2) mapping location, (3) validation hook
8 (docs + acceptance) Slices 1–7 + #505 (1) runtime_id event shape, (2) acceptance assertion, (3) docs file location

This PR (slice 1) does not require any of the above to be decided yet. It is the foundation; the decisions land at their slice boundary.


Why this matters

If this PR is approved without explicit acknowledgement of the open decisions above, the next contributor (or me, in a follow-up session) will pick some answer for each by default. Putting the questions on the record now means:

  • Reviewers can pre-empt the bad answers ("don't use Option A because…").
  • The decisions become explicit choices rather than emergent defaults.
  • Subsequent slice PRs can reference this comment for "this is why we picked X" rather than re-litigating.

Happy to fold this into the PR body if maintainers prefer it on the body itself.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Reviewing commit 5fc2ff6 for PR #538

Review record: d8b49ef2-fd20-41a9-baf7-d4df8ba1b003

Blocking Findings

| # | File:Line | Severity | Finding |
|### Recovery Notes
First recoverable review artifact generated from codex analysis log.

---|-----------|----------|---------|
| 1 | src/ouroboros/config/models.py:279 | BLOCKING | RuntimeProfileConfig hard-rejects claude_code by limiting VALID_RUNTIME_BACKENDS to {"claude","codex","opencode","hermes","gemini"}. That makes the new runtime_profile surface unable to express the documented evaluate -> Claude Code stage mapping from this PR’s own stage contract, so a valid-looking config will fail at startup instead of routing evaluation through the intended runtime. The new tests also cement the narrower behavior by only accepting "claude" in stage mappings. |

Non-blocking Suggestions

None.

Design Notes

The slice is appropriately narrow: enum + config schema + pure resolution helper. The main issue is the config contract is stricter than the runtime vocabulary the design text points contributors toward, so the foundation is not yet safe to build later slices on.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

@shaun0927
Copy link
Copy Markdown
Collaborator Author

Autopilot follow-up: pushed review-addressing commits and confirmed the GitHub check suite is green on the latest head. Current merge state is clean; remaining CHANGES_REQUESTED status appears to be awaiting fresh bot/human re-review rather than failing CI.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Reviewing commit 01e9039 for PR #538

Review record: 47dee817-d541-47ac-ac16-fa9c80c21aa3

Blocking Findings

| # | File:Line | Severity | Finding |
|### Recovery Notes
First recoverable review artifact generated from codex analysis log.

---|-----------|----------|---------|
| 1 | src/ouroboros/config/models.py:320 | BLOCKING | RuntimeProfileConfig._validate_stage_keys() imports ouroboros.orchestrator.stage, but in Python that executes ouroboros/orchestrator/__init__.py first. This validator therefore pulls in the full orchestrator package graph just to validate YAML keys, despite the comment claiming it avoids cycles. Because orchestrator/__init__.py eagerly imports many runtime modules, config parsing for runtime_profile can now fail with unrelated orchestrator/optional-dependency import errors before validation even runs. Given the package already has explicit “partial installs” handling in orchestrator/__init__.py, this is a real startup regression for config-only code paths. The stage constants/error should live in an import-light module, or the validator should avoid package-level orchestrator imports entirely. |

Non-blocking Suggestions

None.

Design Notes

The slice boundary is reasonable: enum + config schema + pure resolution helper. The main architectural problem is that the config layer is no longer isolated; stage validation currently depends on importing the orchestrator package instead of a minimal stage-definition module.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 015fb2c for PR #538

Review record: ce73c031-3a5b-4d5c-8433-e16698c76fca

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Follow-up Findings

  • src/ouroboros/config/models.py:279 [warning] RuntimeProfileConfig validates stage backends against a hard-coded set that excludes backend spellings the orchestrator already accepts (codex_cli, opencode_cli, hermes_cli, gemini_cli in runtime_factory.py). That makes the new config surface reject values the runtime layer can resolve today, so a valid existing backend identifier copied into runtime_profile.default or runtime_profile.stages.* will fail startup validation instead of running. The validator should share the same accepted backend vocabulary as runtime resolution, or normalize through that resolver.

Non-blocking Suggestions

| 1 | src/ouroboros/config/models.py:282 | refactoring | VALID_RUNTIME_PROFILE_STAGE_KEYS duplicates the stage vocabulary already defined in stage.py. Reusing Stage/VALID_STAGE_KEYS would remove one drift point between the schema and resolver. |
| 2 | src/ouroboros/config/models.py:299 | documentation | The docstring says unknown stage keys raise UnknownStageError, but this validator currently raises ValueError directly and never calls parse_stage(). The rendered validation message is fine; the exception type claim is what is inaccurate. |

Design Notes

The slice is intentionally narrow and the resolution helper is simple enough, but the config schema should not invent its own backend contract. This foundation is only safe if stage/backend validation and runtime resolution stay sourced from the same vocabulary.

Policy Notes

  • No in-scope blocking findings remained after policy filtering; downgraded verdict accordingly.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

Copy link
Copy Markdown
Owner

@Q00 Q00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stage enum and per-stage runtime resolution look coherent in isolation, but I am requesting changes because this PR collides with PR #505 on the public orchestrator.runtime_profile contract.

Here runtime_profile is an object used for stage routing. In #505, the same key is a backend profile string used to select the Codex --profile mapping. Merging both as-is creates an incompatible config contract and a real conflict in src/ouroboros/config/models.py.

Please coordinate the stack shape before merge, for example runtime_profile.backend_profile plus runtime_profile.stages, or choose a distinct key for the Codex backend profile.

If #505 is being abandoned or renamed elsewhere, please reply and I will re-check.

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 2, 2026
Coordinate PR Q00#538's stage-routing table with PR Q00#505's backend-native profile selection by reserving runtime_profile.backend_profile and keeping default/stages for stage routing. Move the stage vocabulary to an import-light module so config validation can share it without importing the full orchestrator package graph.

Constraint: PR Q00#538 must coexist with PR Q00#505's Codex backend profile contract while preserving stage routing.

Rejected: Keeping runtime_profile as stage-routing-only object | conflicts with PR Q00#505's string contract.

Rejected: Duplicating stage keys inside config models | drifts from the canonical stage enum.

Confidence: high

Scope-risk: moderate

Directive: Keep backend-native profile selection under runtime_profile.backend_profile and stage routing under runtime_profile.default/stages; import stage vocabulary from ouroboros.orchestrator_stage for config validation.

Tested: uv run ruff format --check src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py

Tested: uv run ruff check src/ouroboros/config/models.py src/ouroboros/config/__init__.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/

Tested: uv run mypy src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py

Tested: uv run pytest tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/ -q

Not-tested: full repository test suite locally

Co-authored-by: OmX <omx@oh-my-codex.dev>
Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 82043eb for PR #538

Review record: b4b9e4ea-183a-4cca-a920-f2da7c9460d1

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

The slice is narrowly scoped and internally coherent: stage vocabulary is isolated in a low-dependency module, config validation fails early for bad stage/backend values, and the compatibility re-export preserves the intended import surface. I did not find a blocking contract mismatch within the changed files.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 3, 2026
Move the Codex worker selector under runtime_profile.backend_profile while preserving the old string shorthand and reserving the stage-routing object fields used by the parallel runtime_profile stack.

Constraint: maintainer review found PR Q00#505 and PR Q00#538 assigned incompatible meanings to orchestrator.runtime_profile.

Rejected: keeping runtime_profile as a Codex-only string | that blocks the stage-routing object contract from sharing the public config key.

Confidence: high

Scope-risk: moderate

Directive: backend-native profile selectors must live under runtime_profile.backend_profile; stage routing owns runtime_profile.default and runtime_profile.stages.

Tested: uv run ruff check targeted runtime-profile files; uv run ruff format --check targeted runtime-profile files; uv run pytest tests/unit/config/test_loader.py tests/unit/cli/test_setup.py tests/unit/orchestrator/test_runtime_factory.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/providers/test_factory.py tests/unit/providers/test_codex_cli_adapter.py -q; uv run mypy targeted runtime-profile files
@shaun0927
Copy link
Copy Markdown
Collaborator Author

@Q00 re-review ping: I re-checked PR #538 against the latest head 82043eb with the previous maintainer concern and the bot design notes in mind. I do not see any remaining code change needed before merge.

What this PR does now:

  • Keeps slice 1 of Stage-Runtime mapping — runtime_profile.{interview,execute,evaluate,reflect} #519 narrowly scoped to the stage-routing primitive: closed Stage vocabulary, stage runtime resolution, RuntimeProfileConfig, and regression tests.
  • Preserves the agreed resolution order: runtime_profile.stages[stage]runtime_profile.default → top-level orchestrator.runtime_backend fallback.
  • Keeps runtime_profile=None as today's behavior: no stage routing override and fallback to the existing orchestrator runtime backend.

Why the previous review concerns appear addressed:

  • The PR feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488) #505 contract collision is resolved by making orchestrator.runtime_profile one object shape: backend_profile is reserved for backend-native profile selection, while default/stages are used for stage routing. The PR feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488) #505 string shorthand is still accepted and coerced to backend_profile, so existing runtime_profile = "worker" style config remains representable.
  • Bot design note about config safety is addressed: stage keys are validated against the closed vocabulary, runtime backend values are validated/normalized, and current runtime aliases such as claude_code, codex_cli, opencode_cli, hermes_cli, and gemini_cli are covered.
  • Bot design note about import safety is addressed: config validation imports the lightweight ouroboros.orchestrator_stage module instead of importing the full ouroboros.orchestrator package graph.
  • The latest bot review on 82043eb is APPROVE with no blocking or non-blocking findings.

Fresh verification I ran locally on this worktree:

  • uv run ruff format --check src/ouroboros/config/models.py src/ouroboros/config/__init__.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py — passed
  • uv run ruff check src/ouroboros/config/models.py src/ouroboros/config/__init__.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/ — passed
  • uv run mypy src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py — passed
  • uv run pytest tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/ -q164 passed

GitHub status at re-check time:

  • Merge state: CLEAN
  • CI: Ruff Lint, MyPy Type Check, Bridge TypeScript, and Python 3.12/3.13/3.14 tests all passing
  • Remaining CHANGES_REQUESTED appears stale against the pre-82043eb contract shape rather than a current blocker.

Based on the above, I think this PR is merge-ready after maintainer re-review/dismissal of the stale requested-changes review.

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 5, 2026
Coordinate PR Q00#538's stage-routing table with PR Q00#505's backend-native profile selection by reserving runtime_profile.backend_profile and keeping default/stages for stage routing. Move the stage vocabulary to an import-light module so config validation can share it without importing the full orchestrator package graph.

Constraint: PR Q00#538 must coexist with PR Q00#505's Codex backend profile contract while preserving stage routing.

Rejected: Keeping runtime_profile as stage-routing-only object | conflicts with PR Q00#505's string contract.

Rejected: Duplicating stage keys inside config models | drifts from the canonical stage enum.

Confidence: high

Scope-risk: moderate

Directive: Keep backend-native profile selection under runtime_profile.backend_profile and stage routing under runtime_profile.default/stages; import stage vocabulary from ouroboros.orchestrator_stage for config validation.

Tested: uv run ruff format --check src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py

Tested: uv run ruff check src/ouroboros/config/models.py src/ouroboros/config/__init__.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/

Tested: uv run mypy src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py

Tested: uv run pytest tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/ -q

Not-tested: full repository test suite locally

Co-authored-by: OmX <omx@oh-my-codex.dev>
@shaun0927 shaun0927 force-pushed the feat/519-1-stage-enum-config branch from 82043eb to 240bf55 Compare May 5, 2026 00:31
Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 240bf55 for PR #538

Review record: 012f42ec-cc2e-4cdc-8adf-11c4e4ac3c7b

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

The slice stays narrow and coherent: the stage vocabulary, config schema, and resolution helper are separated cleanly, and the lightweight ouroboros.orchestrator_stage module avoids pulling config validation into the full orchestrator import graph.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

shaun0927 and others added 4 commits May 5, 2026 09:59
… 1 of Q00#519)

Adds the per-stage runtime selection primitive the Agent OS diagram
agreed in Q00#476 calls for: each pipeline stage (interview / execute /
evaluate / reflect) can be served by a different harness. This first
slice ships the binding-table primitive plus the resolution helper;
subsequent slices wire mcp doctor (5.2/5.3) and the per-runtime
mappings (5.4-5.7).

What's added:

- src/ouroboros/orchestrator/stage.py
    * ``Stage`` StrEnum with the four agreed members. Adding a new
      stage requires an explicit, justified PR (the same narrow-
      membership rule the maintainer alignment in Q00#476 Q1 applied to
      AgentRuntimeContext).
    * ``parse_stage(value)`` raises ``UnknownStageError`` on unknown
      keys; the message names the offending key plus the valid set so
      operators see typos at startup, not mid-workflow.
    * ``resolve_runtime_for_stage(stage, *, stages, default, fallback)``
      resolves in the order locked by the Q00#519 sub-thread:
        1. stages[stage] explicit per-stage mapping
        2. default — runtime_profile.default
        3. fallback — orchestrator.runtime_backend (today's behaviour)

- src/ouroboros/config/models.py
    * ``RuntimeProfileConfig`` Pydantic model with two fields:
        - ``default``: Optional[str]
        - ``stages``: dict[str, str] (validated against ``VALID_STAGE_KEYS``)
    * Field validator raises pydantic ValidationError on unknown stage
      keys at startup so ``[orchestrator.runtime_profile.stages]
      interveiw = "codex"`` fails fast.
    * ``OrchestratorConfig.runtime_profile`` added as
      ``RuntimeProfileConfig | None = None``. Default ``None`` is
      byte-for-byte today's behaviour — that is the BC commitment
      carried forward from PR Q00#505.

- src/ouroboros/config/__init__.py
    * Re-exports ``RuntimeProfileConfig`` alongside the existing
      ``OrchestratorConfig`` re-export.

- tests/unit/orchestrator/test_stage_resolution.py (13 cases)
    * Stage enum has exactly four members; VALID_STAGE_KEYS matches.
    * parse_stage rejects "interveiw" with a helpful message that
      enumerates the valid set.
    * resolve_runtime_for_stage picks stages → default → fallback in
      order.
    * runtime_profile=None preserves byte-for-byte the orchestrator's
      runtime_backend.
    * RuntimeProfileConfig rejects unknown stage keys at validation
      via pydantic ValidationError that surfaces the typo + valid set.
    * Legacy OrchestratorConfig construction (no runtime_profile)
      remains valid; runtime resolution falls through to runtime_backend.

Verification:
- uv run ruff check (clean after one --fix iteration)
- uv run ruff format (2 files reformatted, no logic change)
- uv run pytest tests/unit/orchestrator/test_stage_resolution.py
  (13 passed)
- uv run pytest tests/unit/config/ regression (145 passed)

Stack note:
This PR is independent of Sprints 1/2/3/4 work. It branches off main
and lands on its own track. Subsequent Q00#519 slices (mcp doctor
extensions, per-runtime mappings, docs/acceptance) build on the
``RuntimeProfileConfig`` and ``resolve_runtime_for_stage`` introduced
here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Constraint: Addressed current blocking PR review findings without widening the feature scope.

Confidence: high

Scope-risk: narrow

Directive: Preserve the new regression tests when modifying this contract.

Tested: uv run ruff check src/ouroboros/config/models.py tests/unit/orchestrator/test_stage_resolution.py; uv run pytest tests/unit/orchestrator/test_stage_resolution.py

Not-tested: Full repository matrix; CI remains authoritative.
Constraint: Stage routing validation must reject typos without excluding documented runtime aliases.

Confidence: high

Scope-risk: narrow

Directive: Keep runtime_profile backend validation aligned with the stage contract docs.

Tested: uv run ruff check src/ouroboros/config/models.py tests/unit/orchestrator/test_stage_resolution.py; uv run pytest tests/unit/orchestrator/test_stage_resolution.py

Not-tested: Full repository matrix; CI remains authoritative.
Coordinate PR Q00#538's stage-routing table with PR Q00#505's backend-native profile selection by reserving runtime_profile.backend_profile and keeping default/stages for stage routing. Move the stage vocabulary to an import-light module so config validation can share it without importing the full orchestrator package graph.

Constraint: PR Q00#538 must coexist with PR Q00#505's Codex backend profile contract while preserving stage routing.

Rejected: Keeping runtime_profile as stage-routing-only object | conflicts with PR Q00#505's string contract.

Rejected: Duplicating stage keys inside config models | drifts from the canonical stage enum.

Confidence: high

Scope-risk: moderate

Directive: Keep backend-native profile selection under runtime_profile.backend_profile and stage routing under runtime_profile.default/stages; import stage vocabulary from ouroboros.orchestrator_stage for config validation.

Tested: uv run ruff format --check src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py

Tested: uv run ruff check src/ouroboros/config/models.py src/ouroboros/config/__init__.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/

Tested: uv run mypy src/ouroboros/config/models.py src/ouroboros/orchestrator/stage.py src/ouroboros/orchestrator_stage.py

Tested: uv run pytest tests/unit/orchestrator/test_stage_resolution.py tests/unit/config/ -q

Not-tested: full repository test suite locally

Co-authored-by: OmX <omx@oh-my-codex.dev>
@shaun0927 shaun0927 force-pushed the feat/519-1-stage-enum-config branch from 240bf55 to 6168447 Compare May 5, 2026 00:59
@shaun0927 shaun0927 merged commit f901c91 into Q00:main May 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stage-Runtime mapping — runtime_profile.{interview,execute,evaluate,reflect}

2 participants