Skip to content

feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488)#505

Merged
shaun0927 merged 11 commits into
Q00:mainfrom
shaun0927:feat/orchestrator-runtime-profile
May 5, 2026
Merged

feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488)#505
shaun0927 merged 11 commits into
Q00:mainfrom
shaun0927:feat/orchestrator-runtime-profile

Conversation

@shaun0927
Copy link
Copy Markdown
Collaborator

Summary

Introduces an opt-in orchestrator.runtime_profile setting as the
multi-runtime alternative to PR #488's blunt --ignore-user-config.
Phase 1 ships only the Codex mapping (workercodex exec --profile ouroboros-worker); OpenCode, Hermes, Claude Code, and LiteLLM mappings
are intentionally deferred to follow-up slices, matching the staging in
the design discussion on #488.

Default runtime_profile=None preserves today's _build_command
output exactly in both CodexCliRuntime and CodexCliLLMAdapter
no flag is added, so existing installations see zero behavior change
until they opt in. Setup-managed [mcp_servers.ouroboros] and the new
[profiles.ouroboros-worker] section co-exist in ~/.codex/config.toml,
so isolation becomes selective rather than wholesale and worker
subprocesses no longer need to disable Ouroboros's own MCP/env hookup
to escape interactive Codex defaults.

This implements the four-step slice you proposed on #488:

  1. Add orchestrator.runtime_profile
  2. Add a per-backend mapping (kept module-local; no service-locator)
  3. Have the Codex runtime/adapter pass --profile <mapped-name> only
    when a mapping exists
  4. Leave default behavior unchanged when no runtime profile is configured

What changed

Area Change
config/models.py OrchestratorConfig.runtime_profile: Literal["worker"] | None = None
config/loader.py + __init__.py get_runtime_profile() (env OUROBOROS_RUNTIME_PROFILE → config → None)
orchestrator/codex_cli_runtime.py Module-level _RUNTIME_PROFILE_TO_CODEX_PROFILE = {"worker": "ouroboros-worker"}; __init__ accepts runtime_profile; _build_command injects --profile <name> after exec only when mapped
providers/codex_cli_adapter.py Same shape (mapping table mirrored, with sync comment)
orchestrator/runtime_factory.py + providers/factory.py Codex branch wires runtime_profile=get_runtime_profile(); OpenCode/Hermes/Claude/LiteLLM branches untouched
cli/commands/setup.py New _register_codex_worker_profile() upserts [profiles.ouroboros-worker] idempotently; _setup_codex invokes it after the existing MCP block writer
docs/runtime-guides/codex.md New "Worker subprocess isolation (Agent OS runtime_profile)" section with config + env-var examples

13 files, +432/−10.

Why this instead of --ignore-user-config

ouroboros setup --runtime codex registers an Ouroboros MCP/env hookup
in ~/.codex/config.toml. PR #488's --ignore-user-config would
disable that registration on every Ouroboros-spawned codex exec,
which is the boundary you and @shaun0927 flagged on #488. The
runtime_profile design preserves the MCP/env block intact and
expresses isolation as a selective overlay ([profiles.ouroboros-worker])
that only the runtime layer activates — exactly the "compose, don't
subtract" stance from RFC #476.

The --profile flag also gives us a natural home for #184
(reasoning-tier overrides) and for the OpenCode/Hermes/Claude/LiteLLM
mappings without touching the orchestrator core.

Testing

uv run ruff check src tests              # passes
uv run mypy src/ouroboros                # passes (260 source files)
uv run pytest tests/unit/orchestrator tests/unit/providers \
              tests/unit/config tests/unit/cli
                                          # 1727 passed, 1 skipped

Unit coverage added:

  • runtime / adapter: runtime_profile=None keeps the command shape
    identical to today (regression lock-in); runtime_profile="worker"
    emits --profile ouroboros-worker between exec and --json;
    unmapped values fall back to no flag and log a *.runtime_profile_unmapped
    warning.
  • loader: env-var override, config fallback, default-None.
  • setup: profile section is written, MCP block is preserved with
    correct ordering, and re-running setup is idempotent.
  • _setup_codex end-to-end: now asserts _register_codex_worker_profile
    is invoked alongside the existing _register_codex_mcp_server.

Refs / closes

Phase 2 follow-ups (separate PRs): OpenCode, Hermes, Claude Code,
LiteLLM mappings.

🤖 Generated with Claude Code

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 b2aaf83 for PR #505

Review record: 6ab89508-f4c7-417a-9619-242edf49addb

Blocking Findings

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

---|-----------|----------|---------|
| 1 | src/ouroboros/cli/commands/setup.py:233 | BLOCKING | _upsert_codex_worker_profile_section() rewrites [profiles.ouroboros-worker] to the bare managed header and drops every key already inside that table on each ouroboros setup --runtime codex rerun. That is a real config-loss bug because this PR’s user contract explicitly tells operators to put worker-specific overrides such as model, notify, and sandbox directly in that profile (docs/runtime-guides/codex.md:137). After the next setup/update, those overrides silently disappear and worker subprocesses stop using the intended isolation settings. |

Non-blocking Suggestions

| 1 | src/ouroboros/cli/commands/setup.py:414 | documentation | _print_codex_config_guidance() still says to use ~/.codex/config.toml “only for the Codex MCP/env hookup”, which now contradicts the new worker-profile feature and the runtime guide. |
| 2 | src/ouroboros/orchestrator/runtime_factory.py:77 | tests | The new get_runtime_profile() wiring into create_agent_runtime() is not covered in the changed tests. Existing runtime-factory tests assert permission/backend selection, but nothing verifies that a configured profile is actually propagated into CodexCliRuntime. |
| 3 | src/ouroboros/providers/factory.py:154 | tests | Likewise, the new runtime_profile propagation into CodexCliLLMAdapter has no factory-level regression test. The adapter unit tests cover _build_command(), but not the factory path that now reads config/env and injects the profile automatically. |

Design Notes

The runtime-profile mapping itself is small and sensible, and threading it through both the orchestrator runtime and Codex LLM adapter keeps the contract consistent. The main design problem is lifecycle ownership of the managed profile block: the setup command currently claims ownership of the same table the docs tell users to customize, which makes reruns destructive.


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

@shaun0927
Copy link
Copy Markdown
Collaborator Author

@Q00 — opened this as the `runtime_profile` replacement for #488, following your 4-step scope verbatim. Default-`None` regression-locked across both `_build_command` sites; ruff format/check, mypy (260 files), and the `tests/unit/{orchestrator,providers,config,cli}` slice (1727 passing) are all green locally. Ready for your final pass whenever — no rush.

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 8d807ed for PR #505

Review record: a67ea9bf-1b91-4bdb-a792-a3de976146d9

Blocking Findings

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

---|-----------|----------|---------|
| 1 | src/ouroboros/cli/commands/setup.py:235 | BLOCKING | _upsert_codex_worker_profile_section() deliberately replaces the entire [profiles.ouroboros-worker] block, including any existing keys or subtables. That is a merge blocker because the new Codex guide tells users to put their worker overrides directly in that section, so a routine re-run of ouroboros setup --runtime codex will silently wipe their configured model/sandbox/notify settings. This is user-config data loss in the primary setup path, and the new tests only cover an empty/idempotent block, not preservation of a populated worker profile. |

Non-blocking Suggestions

| 1 | src/ouroboros/cli/commands/setup.py:413 | Documentation | The CLI guidance still says to use ~/.codex/config.toml only for the MCP/env hookup, which now conflicts with the new worker-profile feature and the updated runtime guide. That messaging should be aligned so users know the worker profile also lives there. |
| 2 | src/ouroboros/providers/codex_cli_adapter.py:53 | Refactor | The runtime_profile -> codex_profile mapping now exists in both the orchestrator runtime and the LLM adapter. Centralizing it would reduce drift risk when new profile names are added. |

Design Notes

The change is scoped cleanly around one new orchestrator contract (runtime_profile) and threads it through the two Codex execution paths consistently. The weak point is config ownership: the docs present [profiles.ouroboros-worker] as user-customizable, but setup currently treats that same block as fully managed and destructive.


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.

Findings

  1. High: src/ouroboros/cli/commands/setup.py:231 replaces the entire [profiles.ouroboros-worker] table when setup runs. That conflicts with the docs telling users to customize that exact table in docs/runtime-guides/codex.md:137. Repro: add model, notify, sandbox under [profiles.ouroboros-worker], rerun _register_codex_worker_profile(), and those keys are deleted. Since _setup_codex() calls this on every setup run at src/ouroboros/cli/commands/setup.py:465, upgrades/reconfiguration can silently wipe the worker profile and make --profile ouroboros-worker effectively empty.

Open Question
Is [profiles.ouroboros-worker] meant to be user-editable? If yes, _upsert_codex_worker_profile_section() should preserve existing keys and add a regression test for custom profile values. If no, the docs and inline comments need to point users to the supported customization path.

Verification
Ran targeted checks on the PR worktree: 43 passed. Also confirmed codex exec --help supports --profile on exec. No other blocking issue found in the runtime/adapter command wiring.

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request Apr 29, 2026
…ofile propagation

Adds the missing regression coverage for the issues flagged on PR Q00#505
review:

* setup.py: lock the new merge-preserving worker profile contract.
  - User-authored model/notify/sandbox keys plus a
    [profiles.ouroboros-worker.shell_environment_policy] subtable
    survive a setup rerun.
  - Multiple reruns after a user edit are byte-stable (no key loss
    AND no spurious additions on the first rerun, then idempotent
    afterwards).
  - The most adversarial layout -- a non-managed table abutting the
    worker header with no blank-line separator -- is handled without
    collapsing the predecessor and the worker block.
  - Malformed TOML emits print_error and leaves the file untouched.
* runtime_factory.py / providers/factory.py: assert that
  get_runtime_profile() flows all the way into CodexCliRuntime and
  CodexCliLLMAdapter, both for the "worker" path (mapped to
  ouroboros-worker) and for the default-None path. This prevents a
  silent regression where someone drops the runtime_profile=... kwarg
  from the factory call and breaks worker-subprocess isolation
  without any test failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shaun0927 shaun0927 force-pushed the feat/orchestrator-runtime-profile branch from 8d807ed to 581761a Compare April 29, 2026 15:43
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 581761a for PR #505

Review record: bf2e3911-06a5-4859-9b9b-6ad9b999d1ce

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

| 1 | src/ouroboros/cli/commands/setup.py:231 | Refactoring | _trim_managed_codex_worker_profile_comments() only recognizes the managed header when it is immediately adjacent to [profiles.ouroboros-worker]. If an operator inserts their own comment between the managed block and the table, each ouroboros setup --runtime codex rerun will prepend another managed header block. Consider tolerating that layout or adding a regression test for it. |

Design Notes

The slice is well-contained: config/env resolution, backend-local profile mapping, and setup-time Codex profile registration are kept separate, and both Codex command builders now share the same translation path. I did not find a blocking correctness issue in the changed files.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


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 cd601a4 for PR #505

Review record: da669856-e196-4c70-9de1-bea0d18dd674

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

The slice is internally consistent: config adds an orchestrator-level runtime_profile, both Codex execution paths map it through one shared resolver, and setup now provisions the matching Codex profile block without clobbering user-owned keys. Test coverage for the touched paths is strong. I could not execute the test suite here because pytest is not installed in this environment.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


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

@shaun0927
Copy link
Copy Markdown
Collaborator Author

shaun0927 commented Apr 29, 2026

Autopilot verification for latest head cd601a4:\n\n- Rechecked the prior high-severity review concern: setup now preserves existing keys/subtables in [profiles.ouroboros-worker] and only refreshes the managed comment/header.

The latest bot review approved cd601a4; the remaining CHANGES_REQUESTED decision appears to be the earlier human review state awaiting re-review.\n- Local verification passed: targeted ruff check for changed runtime-profile/setup/factory files and pytest for setup, config loader, Codex runtime/adapter, and factory tests (274 passed).

@Q00 latest head should address the config-loss finding; please re-review when convenient.

@shaun0927
Copy link
Copy Markdown
Collaborator Author

Autopilot status check: latest head remains CI-green and the latest ouroboros-agent review is APPROVED with no remaining bot findings. The PR still shows CHANGES_REQUESTED only because of the earlier human review state; I don't see a current code/design note requiring another commit.

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.

Requesting changes at the stack contract level.

This PR introduces orchestrator.runtime_profile as a Codex backend profile name, for example worker -> --profile ouroboros-worker. PR #538 introduces the same public config key as a RuntimeProfileConfig object for stage routing, with default/stages fields. Those are incompatible meanings for the same config surface, and the two branches currently conflict in src/ouroboros/config/models.py.

Before merge, please coordinate one stable shape, for example runtime_profile.backend_profile plus runtime_profile.stages, or rename the Codex-specific field so the stage-routing object can own runtime_profile.

If #538 is meant to be dropped, or if the contract has moved 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>
shaun0927 and others added 6 commits May 3, 2026 10:00
Adds an opt-in `orchestrator.runtime_profile` setting that lets every
runtime/adapter map a single abstract profile name to its native
configuration mechanism. Phase 1 ships only the Codex mapping
(`worker` → `codex exec --profile ouroboros-worker`); OpenCode, Hermes,
Claude Code, and LiteLLM mappings are intentionally deferred to follow-up
slices.

This is the cooperative, multi-runtime alternative to PR Q00#488's blunt
`--ignore-user-config`: setup-managed `[mcp_servers.ouroboros]` and the
new `[profiles.ouroboros-worker]` section co-exist in `~/.codex/config.toml`,
so isolation is selective rather than wholesale, and worker subprocesses
no longer have to disable Ouroboros's own MCP/env hookup to escape
interactive Codex defaults.

Default `runtime_profile=None` preserves today's `_build_command` output
exactly — covered by regression tests in both runtime and adapter — so
existing installations see zero behavior change until they opt in.

Closes the design discussion on Q00#488.
Refs Agent OS RFC Q00#476, Q00#184.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The orchestrator runtime and the LLM provider adapter had identical
copies of the runtime_profile -> Codex --profile table plus its
warning helper, paired only by a "keep these in sync" comment. Move
both to ouroboros.codex.runtime_profile so the mapping is
single-sourced; future Phase 2 backends (OpenCode, Hermes, Claude
Code, LiteLLM) will provide their own backend-local mapping module.

The new module accepts the caller's logger instance plus a
log_namespace string so the existing
patch("module.log.warning") test seam still captures the
runtime_profile_unmapped warning at its original namespace.

The module lives under ouroboros.codex (rather than
ouroboros.orchestrator) to avoid a circular import:
ouroboros.orchestrator.__init__ pulls in the runner, which pulls in
the providers package -- exactly what imports this module from the
Codex LLM adapter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous _upsert_codex_worker_profile_section() deleted every line
inside [profiles.ouroboros-worker] on each rerun, which was a silent
config-loss path: the new runtime guide explicitly tells operators to
add per-worker overrides (model, notify, sandbox, ...) directly under
that section, but _setup_codex() invokes the upsert on every
ouroboros setup --runtime codex run, so a routine setup/upgrade would
quietly wipe those overrides and leave codex exec --profile
ouroboros-worker resolving an empty profile -- the exact failure mode
the runtime_profile design was supposed to avoid.

Switch the upsert to a refresh-comment / pass-through-body strategy:

* If the table does not exist, append the managed comment block plus
  an empty [profiles.ouroboros-worker] header so codex exec can
  resolve the profile.
* If the table exists, only the managed comment block immediately
  preceding the primary header is rewritten. The body of
  [profiles.ouroboros-worker] and every
  [profiles.ouroboros-worker.<sub>] subtable is preserved verbatim.

This trades aggressive ownership for safe upsert: setup guarantees
the managed header + comment are present and current; the table body
is operator-owned data and survives reruns byte-for-byte.

Also route the new-file path through the same upsert helper so both
branches produce identical output (trailing-newline normalization,
comment-block placement) -- the previous direct-write produced a
slightly different shape than the upsert.

Refresh _print_codex_config_guidance() so it points operators to the
new [profiles.ouroboros-worker] worker overrides as well as the
managed MCP/env hookup, instead of telling them ~/.codex/config.toml
is "only for the MCP/env hookup".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ofile propagation

Adds the missing regression coverage for the issues flagged on PR Q00#505
review:

* setup.py: lock the new merge-preserving worker profile contract.
  - User-authored model/notify/sandbox keys plus a
    [profiles.ouroboros-worker.shell_environment_policy] subtable
    survive a setup rerun.
  - Multiple reruns after a user edit are byte-stable (no key loss
    AND no spurious additions on the first rerun, then idempotent
    afterwards).
  - The most adversarial layout -- a non-managed table abutting the
    worker header with no blank-line separator -- is handled without
    collapsing the predecessor and the worker block.
  - Malformed TOML emits print_error and leaves the file untouched.
* runtime_factory.py / providers/factory.py: assert that
  get_runtime_profile() flows all the way into CodexCliRuntime and
  CodexCliLLMAdapter, both for the "worker" path (mapped to
  ouroboros-worker) and for the default-None path. This prevents a
  silent regression where someone drops the runtime_profile=... kwarg
  from the factory call and breaks worker-subprocess isolation
  without any test failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The post-rebase reviewer bot flagged a residual stacking case in
_trim_managed_codex_worker_profile_comments(): if an operator
inserts their own comment between the managed block and
[profiles.ouroboros-worker], the legacy "match only when adjacent
to the header" trim misses the managed block, and each setup
rerun prepends another fresh managed block above the operator's
comment -- drifting the file with each invocation.

Walk the full output_lines list and remove every contiguous run
that exactly matches the managed comment lines (plus a single
trailing blank line if present). The upsert can then re-emit one
fresh block above the header without stacking, regardless of
where the previous managed block sat relative to the table or
how many stale copies a corrupt earlier rerun may have left
behind.

Adds a regression test that pins the behaviour for the exact
layout the reviewer bot called out: managed block, blank,
operator note, header. After the fix, two reruns produce a
byte-identical file with exactly one managed block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 shaun0927 force-pushed the feat/orchestrator-runtime-profile branch from cd601a4 to 96ea118 Compare May 3, 2026 01:01
@shaun0927
Copy link
Copy Markdown
Collaborator Author

Follow-up for the latest stack-contract review: pushed 96ea118e, rebased on current main, and changed the public config shape so the Codex worker selector now lives at orchestrator.runtime_profile.backend_profile. RuntimeProfileConfig also reserves default and stages so PR #538 can own stage routing under the same object contract instead of conflicting with a string field. The legacy string shorthand still coerces to backend_profile for compatibility.\n\nLocal verification passed: targeted ruff check/format, targeted pytest (288 passed), and targeted mypy. GitHub CI is green and mergeStateStatus is CLEAN; the remaining CHANGES_REQUESTED is the prior review state awaiting re-review.

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 96ea118 for PR #505

Review record: d51b99c9-ac22-4467-a993-707a2b9fddc1

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Follow-up Findings

  • src/ouroboros/config/models.py:288 [warning] RuntimeProfileConfig.backend_profile is hard-coded to Literal["worker"], but the new orchestrator.runtime_profile key is being presented as a shared public contract and the Codex runtime resolver already has an explicit "unknown profile => warn and run without --profile" fallback. With the schema locked to "worker", any config that contains a future profile name, a newer backend slice's value, or even a typo now makes load_config() fail for the entire app instead of taking the intended soft fallback path. This couples a backend-specific phase-1 value into the shared config model and makes the contract brittle across upgrades.

Non-blocking Suggestions

None.

Design Notes

The shared Codex mapping module and the factory wiring are clean, and the worker-profile upsert is much safer than the previous clobbering behavior. The main design issue is that the shared config object now exposes a backend-agnostic shape but still encodes a backend-specific enum in the core schema.

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

Loosen the shared runtime_profile.backend_profile schema so backend-local resolvers, not the core config object, decide whether a profile name maps to a native runtime profile. This preserves the Codex warning/fallback path and keeps the object contract open for future backend slices.

Constraint: Latest bot design note flagged Literal["worker"] as a brittle backend-specific enum inside a shared public config shape.

Rejected: Hard-coding worker in RuntimeProfileConfig | future profile names and unknown-profile fallback would fail at load_config before backend resolution.

Confidence: high

Scope-risk: narrow

Directive: Keep backend_profile as a non-empty string in shared config; perform backend-specific vocabulary checks in backend-local resolvers.

Tested: uv run ruff format src/ouroboros/config/models.py tests/unit/config/test_models.py tests/unit/config/test_loader.py

Tested: uv run ruff check src/ouroboros/config/models.py tests/unit/config/test_models.py tests/unit/config/test_loader.py

Tested: uv run pytest tests/unit/config/test_models.py tests/unit/config/test_loader.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/providers/test_codex_cli_adapter.py -q

Tested: uv run ruff format --check src/ouroboros/config/models.py tests/unit/config/test_models.py tests/unit/config/test_loader.py

Tested: uv run mypy src/ouroboros/config/models.py src/ouroboros/config/loader.py src/ouroboros/codex/runtime_profile.py src/ouroboros/orchestrator/codex_cli_runtime.py src/ouroboros/providers/codex_cli_adapter.py

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 8531671 for PR #505

Review record: 2be88ba8-b347-4a5c-960d-7046e45327f5

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

The slice is coherent: the new runtime_profile contract is centralized in config/model parsing, translated once in a shared Codex resolver, and wired through both Codex execution paths with setup/docs updates to provision the corresponding managed profile. I did not find a scope-local regression or unresolved contract mismatch in the changed files.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


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

@shaun0927
Copy link
Copy Markdown
Collaborator Author

@Q00 re-review ping for PR #505.

I rechecked the latest maintainer/bot review history and pushed one final follow-up commit, 8531671e, to address the last bot design note on the shared orchestrator.runtime_profile contract.

What this PR does now:

  • Adds the Agent OS runtime profile surface for Codex worker subprocess isolation, superseding the earlier fix(codex): ignore user config in subprocesses #488 approach.
  • Keeps default behavior unchanged when orchestrator.runtime_profile is absent / None.
  • Maps runtime_profile.backend_profile = "worker" to Codex --profile ouroboros-worker through the shared Codex resolver used by both Codex execution paths.
  • Provisions [profiles.ouroboros-worker] during setup without owning or clobbering user-authored keys/subtables.
  • Coordinates with PR feat(orchestrator): Stage enum + runtime_profile.stages config (slice 1 of #519) #538 by using one stable object shape:
    • runtime_profile.backend_profile for backend-native profile selection.
    • runtime_profile.default / runtime_profile.stages reserved for stage routing.
    • legacy string shorthand still coerces to backend_profile.

Latest improvement after the bot design note:

  • RuntimeProfileConfig.backend_profile is now a normalized non-empty str | None, not Literal["worker"].
  • That keeps the shared config object backend-agnostic and lets backend-local resolvers handle unknown/future profile names with their intended warning/fallback behavior instead of failing load_config() globally.
  • Added regression coverage for future profile names, whitespace normalization, empty-string rejection, and get_runtime_profile() preserving unknown backend profile values.

Why I think this is merge-ready:

  • The original setup data-loss blocker is addressed by preserving existing [profiles.ouroboros-worker] keys/subtables on rerun.
  • The stack-contract conflict with feat(orchestrator): Stage enum + runtime_profile.stages config (slice 1 of #519) #538 is addressed by the shared object contract above.
  • The latest ouroboros-agent review on 8531671e is APPROVE with no blocking or non-blocking suggestions.
  • GitHub checks are green on the latest head:
    • Ruff Lint
    • MyPy Type Check
    • Bridge TypeScript
    • Python 3.12 / 3.13 / 3.14 tests
  • PR is MERGEABLE; the remaining CHANGES_REQUESTED appears to be the prior maintainer review state awaiting fresh review/dismissal.

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 5, 2026
… 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>
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>
Resolve the PR Q00#505 conflict with the current main profile-stack changes by keeping both Codex task profile anchors and the backend_profile-based worker isolation profile.

Constraint: PR Q00#505 had to remain compatible with the current llm_profiles/profile-anchor work already on main.

Rejected: Drop worker profile setup during conflict resolution | That would reintroduce the runtime_profile contract gap this PR is meant to close.

Rejected: Drop main Codex task profile anchors | They are now part of the current project direction and must coexist with backend_profile.

Confidence: high

Scope-risk: moderate

Directive: Keep orchestrator.runtime_profile.backend_profile backend-local; use llm_profiles for portable task profile routing.

Tested: uv run ruff check src tests

Tested: uv run mypy src/ouroboros

Tested: uv run pytest tests/unit/cli/test_setup.py tests/unit/config/test_loader.py tests/unit/config/test_models.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/orchestrator/test_runtime_factory.py tests/unit/providers/test_codex_cli_adapter.py tests/unit/providers/test_factory.py -q

Tested: uv run pytest -q

Not-tested: GitHub CI after push.

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

Conflict-resolution follow-up pushed in 8ae46ec0.

What changed:

  • Merged current main into PR feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488) #505 and resolved the dirty/conflicting state.
  • Kept the current main Codex task profile anchors (profiles.ouroboros-fast|standard|deep|frontier) and the PR feat(orchestrator): Agent OS runtime_profile (Codex backend, supersedes #488) #505 worker-isolation profile (profiles.ouroboros-worker) side by side.
  • Preserved the stable config split:
    • llm_profiles / llm_role_profiles remain the portable task-profile routing surface.
    • orchestrator.runtime_profile.backend_profile remains the backend-native profile selector used by the Codex resolver.
  • Updated setup/docs/tests so ouroboros setup --runtime codex registers MCP, default Codex task profile anchors, and the worker profile without clobbering existing user-authored profile content.

Local validation on 8ae46ec0:

  • uv run ruff check src tests — passed
  • uv run mypy src/ouroboros — passed (286 source files)
  • Targeted conflict/runtime-profile test slice — 401 passed
  • Full suite — 6142 passed, 2 skipped

ouroboros-agent re-review requested for latest head 8ae46ec0; if CI and review stay green, this should be merge-ready.

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 8ae46ec for PR #505

Review record: c98af883-4257-4b06-836b-6810859afd19

Blocking Findings

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

---|-----------|----------|---------|
| 1 | src/ouroboros/orchestrator/codex_cli_runtime.py:638 | BLOCKING | CodexCliRuntime._build_command() now emits --profile ouroboros-worker up front and can emit a second --profile ... later from _resolve_runtime_codex_config() (line 657) when a role/profile mapping resolves. That means any profiled agent-runtime session can end up with two Codex profiles on one command. Since this code path has only one backend profile surface, one profile must override the other or the CLI will reject the duplicate flag, so the new worker-isolation contract is not actually preserved for role-routed runtime sessions. |
| 2 | src/ouroboros/providers/codex_cli_adapter.py:372 | BLOCKING | The same profile-collision exists in the LLM adapter: _build_command() adds the runtime-level worker profile first, then adds another --profile at line 393 whenever a Codex task profile resolves. Interview/QA/evaluation flows that already rely on llm_profiles/llm_role_profiles will therefore either lose the worker overrides or lose the task profile, depending on how Codex handles repeated flags. The new runtime-profile feature needs an explicit composition strategy here before it is safe to merge. |

Non-blocking Suggestions

None.

Design Notes

The shared config object and shared Codex profile resolver are a sensible direction, and the setup-side ownership boundaries are much cleaner than the earlier clobbering approach. The remaining architectural gap is that the new runtime-level profile and the existing Codex task-profile mechanism both target the same singular CLI flag, so they currently compete instead of composing.


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

Apply repository ruff formatting to the PR Q00#505 conflict-resolution files so CI and local validation use the same formatting gate.

Constraint: GitHub Ruff Lint runs both ruff check and ruff format --check.

Rejected: Leave formatting to CI | The PR must be merge-ready before final review and merge.

Confidence: high

Scope-risk: narrow

Directive: Run ruff format --check alongside ruff check for PR conflict-resolution commits.

Tested: uv run ruff check src tests

Tested: uv run ruff format --check src tests

Tested: uv run mypy src/ouroboros

Tested: uv run pytest tests/unit/cli/test_setup.py tests/unit/config/test_loader.py tests/unit/config/test_models.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/orchestrator/test_runtime_factory.py tests/unit/providers/test_codex_cli_adapter.py tests/unit/providers/test_factory.py -q

Tested: uv run pytest -q

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

Small CI follow-up pushed in db3c195f after the conflict-resolution commit.

Reason:

  • GitHub Ruff Lint also runs ruff format --check; local ruff check had passed, but four conflict-resolution files needed repository formatting.

Validation now passed locally on latest head:

  • uv run ruff check src tests
  • uv run ruff format --check src tests
  • uv run mypy src/ouroboros
  • Targeted conflict/runtime-profile test slice: 401 passed
  • Full suite: 6142 passed, 2 skipped

ouroboros-agent re-review requested for latest head db3c195f.

Resolve the PR Q00#505 post-merge profile collision by letting runtime_profile worker isolation own Codex's singular --profile flag while role/task profile resolution may still provide model fallbacks.

Constraint: Codex CLI exposes one --profile selector, and current main already uses Codex profiles for task-profile routing.

Rejected: Emit both runtime and task --profile flags | The CLI can reject or ambiguously interpret repeated profile selectors.

Rejected: Let task profiles override worker isolation | That would make the new runtime_profile boundary disappear on role-routed worker sessions.

Confidence: high

Scope-risk: moderate

Directive: If Codex adds composable profile layering later, replace this precedence rule with explicit composition tests.

Tested: uv run ruff check src tests

Tested: uv run ruff format --check src tests

Tested: uv run mypy src/ouroboros

Tested: uv run pytest tests/unit/cli/test_setup.py tests/unit/config/test_loader.py tests/unit/config/test_models.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/orchestrator/test_runtime_factory.py tests/unit/providers/test_codex_cli_adapter.py tests/unit/providers/test_factory.py -q

Tested: uv run pytest -q

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

Follow-up for the latest bot review has been pushed in 9d1fb7ef.

What changed:

  • Made the Codex profile precedence rule explicit for both execution paths.
  • orchestrator.runtime_profile.backend_profile=worker now owns Codex's singular --profile ouroboros-worker flag when configured.
  • Role/task profile resolution no longer emits a second competing --profile; it may still contribute a model fallback where applicable.
  • Added regressions for both CodexCliRuntime and CodexCliLLMAdapter proving only one --profile is emitted when worker isolation and task/role profiles both resolve.

Validation on latest head:

  • uv run ruff check src tests
  • uv run ruff format --check src tests
  • uv run mypy src/ouroboros
  • Targeted conflict/runtime-profile slice: 403 passed
  • Full suite: 6144 passed, 2 skipped

ouroboros-agent re-review requested for latest head 9d1fb7ef.

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 9d1fb7e for PR #505

Review record: 28acd47b-d1cf-4419-8e89-6bb34cc6ea64

Blocking Findings

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

---|-----------|----------|---------|
| 1 | src/ouroboros/cli/commands/setup.py:610 | BLOCKING | _register_codex_worker_profile() only checks that the existing file is syntactically valid TOML before appending [profiles.ouroboros-worker], but it never validates that profiles is actually a table or that the post-write document still parses. A valid user config like profiles = "oops" currently passes tomllib.loads(raw), then this code writes a new table under profiles, producing invalid TOML (Cannot overwrite a value). That silently corrupts ~/.codex/config.toml on ouroboros setup --runtime codex, which is a production-breaking config write. _register_codex_default_profiles() already guards this case via _existing_codex_profile_names(); the worker-profile path needs equivalent validation or a post-update parse check before writing. |

Non-blocking Suggestions

None.

Design Notes

The runtime-profile wiring is otherwise coherent: the shared Codex resolver keeps the orchestrator/runtime and provider paths aligned, and the “worker profile owns the single Codex --profile flag” rule is consistently enforced in both command builders. The main weakness is setup-path robustness: the new worker-profile registrar is not yet as defensive as the existing default-profile registrar when mutating arbitrary user TOML.


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

Make Codex worker-profile registration use the same profiles-table validation as default profile anchors, and parse the generated TOML before touching user config.

Constraint: Bot review found syntactically valid scalar profiles values could be corrupted by appending a nested profile table.

Rejected: Rely on initial tomllib.loads only | That proves syntax, not that the profiles namespace can accept profile tables.

Confidence: high

Scope-risk: narrow

Directive: Validate arbitrary user TOML structure before any setup-managed profile write.

Tested: uv run ruff check src tests

Tested: uv run ruff format --check src tests

Tested: uv run mypy src/ouroboros

Tested: uv run pytest tests/unit/cli/test_setup.py tests/unit/config/test_loader.py tests/unit/config/test_models.py tests/unit/orchestrator/test_codex_cli_runtime.py tests/unit/orchestrator/test_runtime_factory.py tests/unit/providers/test_codex_cli_adapter.py tests/unit/providers/test_factory.py -q

Tested: uv run pytest -q

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

Follow-up for the latest bot setup-path finding has been pushed in e69e04f0.

What changed:

  • _register_codex_worker_profile() now validates the existing profiles namespace with the same helper used by default Codex profile anchors.
  • It also parses the generated TOML before writing, so setup will not corrupt valid-but-incompatible user TOML such as profiles = "oops".
  • Added a regression that proves worker-profile setup skips that scalar profiles case without changing the file.

Validation on latest head:

  • uv run ruff check src tests
  • uv run ruff format --check src tests
  • uv run mypy src/ouroboros
  • Targeted conflict/runtime-profile slice: 404 passed
  • Full suite: 6145 passed, 2 skipped

ouroboros-agent re-review requested for latest head e69e04f0.

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 e69e04f for PR #505

Review record: d5bf8736-1631-4c6f-9fd8-79facfe33394

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

The change is coherent: orchestrator.runtime_profile stays backend-agnostic at the config layer, Codex-specific mapping is isolated in a shared resolver, and both Codex execution paths now enforce a single-profile precedence rule consistently. The setup-side TOML mutation is also appropriately conservative and preserves user-owned profile content.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


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

@shaun0927 shaun0927 merged commit 2bd3806 into Q00:main May 5, 2026
6 checks passed
shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 5, 2026
… 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>
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>
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.

2 participants