Skip to content

feat(routing): artifact-aware engine routing via output_requirement (TD-197)#41

Merged
engkimo merged 2 commits into
mainfrom
feature/artifact-aware-routing
Jun 4, 2026
Merged

feat(routing): artifact-aware engine routing via output_requirement (TD-197)#41
engkimo merged 2 commits into
mainfrom
feature/artifact-aware-routing

Conversation

@engkimo

@engkimo engkimo commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Problem

Artifact-producing subtasks (slide / file / code / data) were routed to Ollama by the regex `SubtaskTypeClassifier` — e.g. "Create a PPTX slide file" classified as `simple_qa` → ollama, which cannot create files and degenerates into repetitive tool loops. The LLM-derived `output_requirement` was already computed by the bypass classifier but discarded at node-execution routing time.

Confirmed live: the slide goal decomposed correctly but every artifact node ran on Ollama and the task timed out with no file produced.

What changed

  • `SubTask` gains `output_requirement: OutputRequirement | None` (additive, default `None`).
  • `NodeExecutor.to_subtask` and `_build_task` propagate it — `_build_task` is the routing-critical path that feeds the inner engine (easy to miss).
  • `AgentEngineRouter.select_for_output`: FILE → OpenHands, CODE → Codex, DATA → Gemini; TEXT / None → delegate to the task-type router; `budget <= 0` keeps the LOCAL_FIRST Ollama guard.
  • LangGraph `_execute_batch` consults `output_requirement` before the regex path; TEXT/None is byte-identical to today.
  • `FractalTaskEngine` classifies each terminal node's requirement per-node via the existing LLM `OutputRequirementClassifier` (gated on the goal being an artifact, so pure-text goals add no LLM calls), replacing the blanket goal-inheritance loop. Wired via the container.

No new regex heuristics — routing keys off the LLM `OutputRequirement` enum (aligns with the no-rule-based-heuristics principle).

Testing

  • 3380 unit tests pass (+ new: router `select_for_output`, SubTask field, `to_subtask`/`_build_task` propagation, `_execute_batch` artifact branch, per-node classification). ruff clean.

Notes

  • SDD artifacts under `specs/artifact-aware-routing/`.
  • Known follow-up (out of scope, tracked): fractal over-planning on slow Ollama still exhausts the hard timeout before artifact nodes execute end-to-end — needs planner-on-Haiku. This PR fixes where artifact nodes route, not how long orchestration takes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced routing system now intelligently directs tasks to appropriate execution engines based on artifact output requirements (files, code, data), optimizing performance and resource allocation. Tasks automatically select specialized engines when producing artifacts while using efficient execution paths for text-only operations.
  • Documentation

    • Added comprehensive specification and implementation plan for artifact-aware engine routing.

engkimo and others added 2 commits June 2, 2026 13:55
…TD-197)

Artifact-producing subtasks (slide/file/code/data) were routed to Ollama by
the regex SubtaskTypeClassifier (e.g. 'Create a PPTX slide file' -> simple_qa
-> ollama), which cannot create files and loops. The LLM-derived
output_requirement was computed by the bypass classifier but discarded at
node-execution routing.

- SubTask gains output_requirement; NodeExecutor.to_subtask propagates it.
- AgentEngineRouter.select_for_output maps FILE->OpenHands, CODE->Codex,
  DATA->Gemini; TEXT/None delegate to the task-type router; budget<=0 keeps
  the LOCAL_FIRST Ollama guard.
- LangGraph _execute_batch consults output_requirement before the regex path.
- FractalTaskEngine classifies each terminal node's requirement per-node
  (LLM, gated on the goal being an artifact) instead of blanket goal
  inheritance, so a 'search' subnode stays on Ollama while the file node
  escalates. Wired via OutputRequirementClassifier in the container.
- SDD artifacts under specs/artifact-aware-routing/.

3373 unit tests pass (+ new), ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…k (TD-197)

_build_task feeds the inner engine and is the routing-critical path, but it
dropped output_requirement (only to_subtask, used for SSE display, carried it).
So artifact nodes still reached the inner engine with output_requirement=None
and fell through to the regex simple_qa->ollama path. Propagate it + add
engine-level tests proving FILE subtasks route to OpenHands and TEXT does not
escalate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR implements artifact-aware engine routing (TD-197), enabling LLM engine selection based on output requirements (file/code/data artifacts) inferred during node classification. Changes flow from domain contracts through subtask propagation to routing in both task graph and fractal execution paths, with optional per-node classification for artifact-producing goals.

Changes

Artifact-Aware Engine Routing

Layer / File(s) Summary
Specification and planning documentation
specs/artifact-aware-routing/spec.md, specs/artifact-aware-routing/plan.md, specs/artifact-aware-routing/tasks.md
Feature specification, implementation plan, and task breakdown for TD-197, covering architecture decisions, routing behavior, per-node classification, test strategy, and rollout guidance.
Domain data model: SubTask output requirement field
domain/entities/task.py
SubTask Pydantic model extends with optional output_requirement: OutputRequirement | None field to carry artifact classification through execution.
Router artifact-to-engine mapping and select_for_output method
domain/services/agent_engine_router.py, tests/unit/domain/test_agent_engine_router.py
_OUTPUT_TO_ENGINE map routes file/code/data artifacts to specific engines; new select_for_output(output_requirement, budget, task_type) method selects engines with budget override and task-type fallback; unit tests validate routing for all artifact types, TEXT delegation, and budget forcing.
Node executor: propagating output requirement to subtasks
infrastructure/fractal/node_executor.py, tests/unit/infrastructure/test_node_executor.py
NodeExecutor.to_subtask and _build_task forward output_requirement from PlanNode to created SubTask instances; tests verify propagation and null-default behavior.
Task graph engine: artifact-aware routing branch
infrastructure/task_graph/engine.py, tests/unit/infrastructure/test_task_graph_engine.py
Subtask auto-engine selection prefers select_for_output when subtask.output_requirement is artifact-producing, otherwise uses task-type-based select; logging updated; integration tests verify routing and non-routing paths.
FractalTaskEngine: per-node output requirement classification
infrastructure/fractal/fractal_engine.py, tests/unit/infrastructure/test_fractal_engine.py
Optional OutputRequirementClassifier support for gated per-node artifact classification of terminal nodes when goal requirement is artifact-producing; stores goal-level requirement, calls classifier before execution, handles errors non-fatally; tests cover artifact/text goals, no-classifier paths, idempotency, and error handling.
Container dependency injection: classifier instantiation and wiring
interface/api/container.py
AppContainer._create_task_engine instantiates OutputRequirementClassifier and wires into FractalTaskEngine via output_classifier parameter for fractal execution mode.

Sequence Diagram(s)

sequenceDiagram
  participant Request as Client Request
  participant Planner as Node Planner
  participant Executor as Node Executor
  participant Router as Agent Engine Router
  participant Classifier as OutputRequirementClassifier
  participant Engine as FractalTaskEngine
  
  Request->>Planner: Submit task
  Planner->>Planner: Classify node outputs
  Planner->>Executor: Emit PlanNode with output_requirement
  Executor->>Executor: Convert to SubTask, copy output_requirement
  Executor->>Router: Route via select_for_output(output_requirement)
  alt output_requirement is artifact
    Router->>Router: Check _OUTPUT_TO_ENGINE mapping
    Router-->>Executor: Return artifact-capable engine
  else output_requirement is TEXT or None
    Router->>Router: Delegate to select(task_type)
    Router-->>Executor: Return task-type engine
  end
  Executor->>Engine: Execute with routed engine
  Engine->>Classifier: Classify for per-node artifact escalation
  Classifier-->>Engine: output_requirement (file/code/data)
  Engine->>Engine: Execute terminal node with classification
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • engkimo/open-morphic#31: Both PRs modify fractal execution flow around OutputRequirement classification in infrastructure/fractal/fractal_engine.py and DI wiring, but in potentially conflicting ways—main PR adds separate output_classifier while retrieved PR removes that dependency by folding into bypass classifier.

Poem

🐰 Routes and classifications bloom,
Artifacts find their native room,
Per-node cascades from goal to leaf,
Budget rules and fallback relief—
TD-197 complete at last,
Engine wisdom unsurpassed!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing artifact-aware engine routing via the output_requirement field to address incorrect artifact-producing subtask routing to Ollama.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/artifact-aware-routing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@engkimo engkimo merged commit db35743 into main Jun 4, 2026
4 of 6 checks passed
@engkimo engkimo deleted the feature/artifact-aware-routing branch June 4, 2026 06:31
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.

1 participant