feat(impl): mixture-of-agents modes — generative + advisory (#668)#669
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…idates Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…allback Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…through server graph Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives the production server graph (OrchestratorService.start_workflow -> GraphRunner.create_server_graph(profile=...) -> MoA graph) with real Architect/Aggregator/nodes, mocking only ApiDriver.execute_agentic. Surfaced and fixed a wiring bug: moa_proposers_node and aggregator_node did not persist the plan from the agent's write_plan tool calls (unlike call_architect_node), so under a mocked LLM boundary the proposer/canonical plan files never landed on disk and the aggregator/validator failed with FileNotFoundError. Both nodes now fall back to _resolve_plan_from_tool_calls to guarantee the file-on-disk plan contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Concurrent Developer proposers run in isolated worktrees, then an Aggregator selects the best candidate and applies its diff to the primary worktree. Proposer count/models configurable via MoAConfig. Individual proposer failures degrade gracefully. Closes #668
Inline aggregator logic into moa.py, extend state fields, and add test coverage for review findings. 42 tests pass, ruff and mypy clean.
Refine graph wiring, routing, and state transitions. Add shared test fixtures in conftest.py. Simplify aggregator/proposer test stubs. 42 tests pass, ruff and mypy clean.
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (37)
WalkthroughThis PR adds a Generative Mixture-of-Agents (MoA) execution path to the implementation pipeline. New 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
tests/unit/pipelines/test_generative_moa_graph_wiring.py (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPlace this under
tests/unit/pipelines/implementation/to mirror the source module.This file exercises
amelia/pipelines/implementation/graph.pyandrouting.py, but it lives one level higher intests/unit/pipelines/. Moving it under the mirroredimplementation/test package will keep discovery and ownership consistent. As per coding guidelines, tests should mirror module structure intests/with subdirectories forunit/,integration/,e2e/, andperf/.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/pipelines/test_generative_moa_graph_wiring.py` at line 1, This test file is in the wrong place and should be moved to mirror the source module structure under the implementation test package. Relocate the generative MoA graph wiring test into tests/unit/pipelines/implementation/ so it aligns with the code it covers, specifically the graph.py and routing.py modules, and keep the existing test name/content intact after the move.Source: Coding guidelines
amelia/pipelines/implementation/routing.py (1)
30-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeduplicate
resolve_moa_config.The same helper body also exists in
amelia/pipelines/implementation/moa.py, so routing and proposer execution can drift the next time MoA option parsing changes. Prefer a single shared definition and import it from both call sites.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@amelia/pipelines/implementation/routing.py` around lines 30 - 45, Deduplicate the MoA config resolver by making resolve_moa_config a single shared implementation instead of keeping identical copies in routing.py and moa.py. Move or centralize the helper in one module, then update both the routing path and the proposer execution path to import and use that shared resolve_moa_config so MoA option parsing stays consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@amelia/core/types.py`:
- Around line 344-377: MoAConfig currently allows unknown fields to be ignored,
so typos in the “moa” options silently fall back to the default config. Update
MoAConfig’s validation in core/types.py to forbid extra keys at the config
boundary, and keep from_options() using model_validate() so invalid dicts fail
fast instead of being treated as disabled. Use the MoAConfig class and its
from_options() method as the touchpoints for the fix.
In `@amelia/pipelines/implementation/moa.py`:
- Around line 148-176: The scratch worktree commit path in
_commit_scratch_worktree currently drops any non-zero git commit result, so
hook-driven rewrites can cause a valid MoA candidate to be lost. Update
_commit_scratch_worktree to use the same hook-aware retry behavior as
commit_task_changes(): after a failed commit, restage the worktree changes and
retry the commit before returning None, keeping the existing _run_git and
logger.debug flow for unexpected failures.
In `@amelia/pipelines/implementation/state.py`:
- Around line 125-128: `call_developer_node` currently clears
`driver_session_id` but leaves the MoA fields in `ImplementationState`, so stale
`generative_moa_selected` / `generative_moa_candidates` can block
`commit_task_changes` on retries or later tasks. Update the state reset in the
developer-path transition to explicitly clear both MoA fields when returning to
`developer_node`, using the existing `call_developer_node` flow and the
`ImplementationState` fields `generative_moa_selected` and
`generative_moa_candidates`.
In `@amelia/pipelines/implementation/utils.py`:
- Around line 409-416: The commit skip in commit_task_changes is too broad
because state.generative_moa_selected persists after the MoA task and can cause
later tasks to bypass commits incorrectly. Scope this check to the task that
actually created the MoA selection by either clearing the flag after the MoA
task completes or storing the originating task id alongside the selected
candidate and comparing it before returning early. Use the existing
commit_task_changes logic and state.generative_moa_selected reference to keep
the skip limited to the correct task.
---
Nitpick comments:
In `@amelia/pipelines/implementation/routing.py`:
- Around line 30-45: Deduplicate the MoA config resolver by making
resolve_moa_config a single shared implementation instead of keeping identical
copies in routing.py and moa.py. Move or centralize the helper in one module,
then update both the routing path and the proposer execution path to import and
use that shared resolve_moa_config so MoA option parsing stays consistent.
In `@tests/unit/pipelines/test_generative_moa_graph_wiring.py`:
- Line 1: This test file is in the wrong place and should be moved to mirror the
source module structure under the implementation test package. Relocate the
generative MoA graph wiring test into tests/unit/pipelines/implementation/ so it
aligns with the code it covers, specifically the graph.py and routing.py
modules, and keep the existing test name/content intact after the move.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 45880543-fff5-40a6-a68b-ad61e521def4
📒 Files selected for processing (12)
amelia/core/types.pyamelia/pipelines/implementation/graph.pyamelia/pipelines/implementation/moa.pyamelia/pipelines/implementation/routing.pyamelia/pipelines/implementation/state.pyamelia/pipelines/implementation/utils.pytests/unit/core/test_moa_config.pytests/unit/pipelines/conftest.pytests/unit/pipelines/implementation/test_state.pytests/unit/pipelines/test_generative_moa_aggregator_node.pytests/unit/pipelines/test_generative_moa_graph_wiring.pytests/unit/pipelines/test_generative_moa_proposers_node.py
…gatorResult The Aggregator agent previously took and returned ImplementationState, coupling the agent layer to a pipeline-level type. Introduce an AggregatorResult dataclass carrying the raw agentic outputs (tool calls/results, raw output, plan path, error) and have aggregate() accept an issue_id instead of the full state. aggregator_node builds its own state update from the result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grafts the advisory Mixture-of-Agents implementation (Architect proposer fan-out + LLM Aggregator plan synthesis at the planning stage) into the generative MoA branch. Issue #668 describes advisory MoA as the sibling of generative MoA; this branch's routing previously stubbed advisory mode with a not-implemented warning — feat/moa-pipeline implements it. Conflict resolution: - amelia/core/types.py: both branches defined MoAConfig; kept this branch's definition (frozen, tuple proposer_models, robust from_options). Semantics are reconciled in follow-up commits. - amelia/pipelines/implementation/graph.py: kept both wirings — the build-time architect-stage swap (advisory) and the runtime post-approval routing (generative). They are independent stages and compose. - tests/unit/core/test_moa_config.py: kept this branch's version; its coverage is a superset of the incoming one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With both MoA stages implemented, the mode field is redundant with where the config lives: options['moa'] on the architect agent activates advisory planning MoA, and on the developer agent activates generative MoA. Placement expresses intent exactly, and removes the contradictory states the enum allowed (e.g. mode=advisory on the developer agent, which previously warned and fell back). Also adopt proposer_count default of 2 from the advisory branch: a mixture of one agent isn't a mixture, so enabling MoA without an explicit count should actually fan out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With generative_moa_* nodes in the same graph, the unprefixed advisory names (moa_proposers_node, aggregator_node, moa_proposer_plans) wrongly read as the only MoA. Rename to advisory_moa_* so every MoA node, state field, and test file names its stage. Also drop leftover work-log breadcrumbs and line-number references from the grafted docstrings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion test The default model 'anthropic/claude-3.5-sonnet' is deprecated on OpenRouter (404 'No endpoints found'), failing the integration gate. Switch to 'anthropic/claude-sonnet-4' — the single-provider id the CLI agentic real-call test already uses — avoiding flaky free-tier provider routing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Forbid unknown keys in MoAConfig so config typos fail fast at the boundary - Retry scratch-worktree commits once after hook rewrites, mirroring commit_task_changes - Clear generative MoA candidates/selection in call_developer_node so a stale selection can't skip commits on reviewer retries or subsequent tasks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add MoAConfig (enabled, proposer_count, proposer_models, isolation) to amelia/core/types.py as the concrete case for per-agent options. CLI: new 'profile update' command with --agent, MoA flags, and --clear-options. Options dict is generic plumbing, not moa-specific. Dashboard: AgentFormData gains options field, useProfileForm threads it through to API payloads, AgentsSection shows a collapsible MoA options panel per agent row, ProfileDetailPage wires the callback. Fix pre-existing useRecentModels.test.ts localStorage mock failures (Storage.prototype spy broken in current vitest/jsdom). Tests: 7 CLI test cases, 6 MoAConfig validation cases, dashboard form and component tests for the new options UI.
- CLI: only validate/write the MoA block when a --moa-* flag is passed, so existing options (sibling keys and unknown future MoA keys) survive an update instead of being clobbered by a default block. - Server: validate the MoA sub-config via MoAConfig on agent create. - Dashboard: hold proposer count as local string state so clearing or partial typing no longer snaps to the default; clamp to [1,10] on blur. - Dashboard: deep-compare agent options (order-insensitive, undefined as missing) so key ordering doesn't mark the profile form spuriously dirty. - Tests: cover options-preservation and drop now-duplicate MoAConfig tests. Daydream-Run: 20260704000908-9cb67a26 Daydream-Version: 0.20.0
Delete the per-agent-options MoAConfig (was at line 121, isolation: bool) in favor of the full pipeline MoAConfig (isolation: MoAIsolation). Convert CLI/routes boolean isolation to 'worktree' string. - types.py: Remove duplicate MoAConfig from agent-options merge - cli/config.py: Convert --moa-isolation bool to MoAIsolation.WORKTREE - routes/settings.py: Convert dashboard boolean isolation before validation
🔀 Merged: per-agent MoA options (CLI + dashboard)Merged CLI
Server
Dashboard
Conflict resolution
Gate: 2743 passed, ruff clean, mypy clean, dashboard builds. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
… on startup Importing the uvicorn entrypoint (amelia.server.main) pulls in knowledge.repository -> database -> migrator. The migrator imported PROMPT_DEFAULTS from the agents layer at module scope, chaining agents -> tools -> discover_builtin_tools -> tools.knowledge -> knowledge.repository while the latter was partially initialized. The circular import made discover_builtin_tools silently drop the knowledge_search tool at startup. Move the PROMPT_DEFAULTS import into initialize_prompts() (its only use site), removing the database->agents module-scope edge and fixing the layering inversion. Add a subprocess regression test that drives the production import order and asserts knowledge_search registers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Motivation
Closes #668
Generative tasks (feature implementation, bug fixes) often have multiple valid approaches. Mixture-of-agents lets multiple Developer agents independently attempt the same task in isolated worktrees, then selects the strongest candidate — improving output quality through diversity while preserving isolation between concurrent attempts.
Changes
Added
MoAConfigdataclass intypes.pywith validation (proposer count, model selection, isolation settings)GenerativeMoACandidatestate model instate.pygenerative_moa_proposers_node— concurrent fan-out across N isolated worktreesgenerative_moa_aggregator_node— candidate selection + diff applicationgraph.pywith routing function to enable/disable MoA modeutils.pyChanged
routing.pyupdated to route through MoA nodes when enabledstate.pyextended with MoA candidate tracking fieldsTest Plan
tests/unit/pipelines/test_generative_moa_*.py,tests/unit/core/test_moa_config.py)uv run ruff check .— cleanuv run mypy .— clean (no issues in 205 source files)uv run pytest tests/unit— 2701 passed)Checklist
uv run pytest)uv run ruff check)uv run mypy)Follow-up Changes (2026-07-02): advisory MoA merged in
This PR now also carries the advisory MoA planning pipeline (the sibling
feature described in #668's background), reconciled from
feat/moa-pipelineinto a single implementation. The two MoA stages are independent and compose:
options['moa']on the architect agent): N Architectproposers plan the same issue concurrently, each writing a distinct
-proposerNplan file; anAggregatoragent (amelia/agents/aggregator.py)synthesizes one canonical plan via
write_planforplan_validator_node.A single surviving proposer is promoted directly without synthesis. Wired at
graph-build time — the resolved profile is threaded through
GraphRunner.create_server_graph/ImplementationPipeline.create_graph.options['moa']on the developer agent): unchangedfrom the original PR — N Developer proposers in isolated worktrees, first
successful candidate diff applied atomically.
Reconciliation decisions
MoAConfig, stage selected by placement. Themodeenum(
advisory|generative) is gone: config on the architect agent meansadvisory, on the developer agent means generative. Placement expresses
intent exactly and removes contradictory states (e.g.
mode=advisoryonthe developer, which previously warned and fell back).
proposer_countdefaults to 2 (was 1): a mixture of one agent isn't amixture; enabling MoA without an explicit count should actually fan out.
Aggregator.aggregate()yields
AggregatorResult(raw agentic outputs); the node builds its ownstate update, so the agent has no dependency on
ImplementationState.advisory_moa_proposers_node/advisory_moa_aggregator_node/advisory_moa_proposer_plans, mirroring thegenerative_moa_*names.aggregatoragent seeded byamelia config init(not added toREQUIRED_AGENTS; the node falls back to the architect config for legacyprofiles), plus an
aggregator.systemprompt default and tool profile.New tests
tests/integration/test_advisory_moa_pipeline.py— end-to-end advisory MoAthrough the production
OrchestratorService+ real Postgres + realLangGraph run; only
ApiDriver.execute_agentic(the LLM boundary) ismocked. Covers 2-proposer synthesis and best-effort survivor promotion.
conditional graph wiring, server profile threading, and config CLI seeding.
Verification
uv run ruff check amelia tests— cleanuv run mypy amelia— clean (206 files)uv run pytest— 2728 passeduv run pytest -m integration— 407 passed, 4 skipped (includes the newadvisory MoA end-to-end test; one unrelated-looking failure from a
deprecated OpenRouter model id was fixed here via cherry-pick
aa5c182f)