Skip to content

feat: unified runtime state, remove task graph, and add tool API - #366

Merged
MrtinoRG merged 4 commits into
mainfrom
new_state
Jul 13, 2026
Merged

feat: unified runtime state, remove task graph, and add tool API#366
MrtinoRG merged 4 commits into
mainfrom
new_state

Conversation

@MrtinoRG

@MrtinoRG MrtinoRG commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Core changes

The changes in this PR only include the src code. When the PR gets accepted I will push the tasks update. This is to keep the PR reviewable and focused on the core refactor, which is already quite large.

Runtime state (CorralState)

  • Introduce src/corral/backend/state.py with a single CorralState dataclass as the one mutable runtime object per run
  • Remove scattered state from Environment (trial_states, trial_counter, workspace) and TaskGroup (results, scores) — all now live in CorralState
  • Environment, tools, scoring, and the server all read/write through CorralState; task definitions remain immutable config

Task graph & dependency handling

  • Remove TaskGroup as a mutable runtime object; replace with an immutable TaskGraph / helper functions (topological_order, validate_task_graph, ready_tasks)
  • Add InputRef + input_map on TaskDefinition for explicit, named dependency wiring (replaces the magic result_from_<task_id> key convention)
  • CorralState resolves dependency inputs via resolve_inputs(task) and stores per-task outputs in task_runs[task_id]

Execution order & correctness

  • Runner now enforces topological execution order in chained mode (previously relied on authoring order being correct)
  • Add assert_dependencies_selected — raises a clear error up front if a selected subset omits a required dependency
  • Drop the eager get_task_prompt() call at env construction; prompts are now only resolved after dependencies are satisfied, making NOT YET AVAILABLE placeholders in prompts unnecessary and removed
  • Broken chain short-circuit: when an upstream task fails, downstream tasks are recorded as score=0.0 / unreachable with 0 iterations — no agent call, no exception

Tool API cleanup

  • Replace four tool parameters (available_tools, common_tools, file_tools, file_tool_factory) with a single Toolset value object on Environment.__init__ and build_environments
  • Toolset.resolve(task, workspace) collapses _add_task_tools + _setup_file_tools into one call per trial reset
  • Add excluded_tools to base TaskDefinition; wetlab's _add_task_tools override is deleted

Other cleanups

  • Single vs. chained tasks share one authoring path (TaskDefinition with optional input_map); group_id is no longer a public argument
  • Grouping (connected components) derived automatically by the framework
  • prompt_fn / setup_fn hooks on TaskDefinition replace per-benchmark Environment subclasses for custom prompts and hardware setup

Summary by Sourcery

Unify runtime task state management around a new CorralState, replace mutable task groups with an immutable task graph and dependency API, and simplify tool configuration into a single Toolset while updating routing, server, and tests accordingly.

Enhancements:

  • Refactor Environment to use a shared CorralState and TaskRunState for all mutable runtime data, including trials, tool calls, and submissions.
  • Replace TaskGroup and implicit dependency wiring with TaskDefinition-based dependency graphs, explicit InputRef/input_map, and topological ordering utilities, including connected components and validation.
  • Introduce a Toolset value object to centralize tool configuration, including common, pool, and workspace-bound tools, and update Environment and tool resolution to use it.
  • Ensure chained runs execute in dependency order with upfront dependency-closure checks, and short-circuit downstream tasks as unreachable when dependencies fail, without invoking the agent.
  • Simplify LaTeX generation and server APIs to derive grouping and dependencies from TaskDefinition and CorralState instead of TaskGroup, including a new dependency_graph endpoint.

Tests:

  • Add tests for dependency graph validation, topological ordering, and chained short-circuit behaviour, and update existing environment and configure_additional_apps tests to the new APIs.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dependency graph support, including validation and dependency-closed workflow execution.
    • Chained workflows now short-circuit: if an upstream task fails or is missing output, downstream tasks are marked unreachable instead of running.
  • Refactor

    • Updated the task model to use immutable task definitions and explicit input wiring between tasks.
    • Centralized runtime/session state handling and improved prompt/export behavior for chained runs.
  • Bug Fixes

    • Trial listing and status responses now reflect the latest completed trial and consistent tool statistics.
  • Documentation / Tests

    • Updated docs and added/adjusted tests for dependency ordering and chained short-circuit behavior.

@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors runtime state into a single CorralState, replaces mutable TaskGroup with an immutable task graph and explicit InputRef-based dependencies, enforces topological execution with chain short-circuiting, and introduces a unified Toolset API while updating server/router and tests accordingly.

Sequence diagram for chained execution with dependency checks and short-circuiting

sequenceDiagram
    participant Runner as RunBenchmark
    participant Router as RouterClient
    participant Server as BackendServer
    participant Env as Environment

    Runner->>Router: get_dependency_graph()
    Router->>Server: GET /dependency_graph
    Server-->>Router: {task_id: [deps]}
    Router-->>Runner: graph

    Runner->>Runner: assert_dependencies_selected(task_ids, graph)
    Runner->>Runner: ordered_ids = order_selected(task_ids, graph)

    loop trial_round over trials_per_task
      loop task_id in ordered_ids
        Runner->>Runner: missing = _unsatisfied_dependency(task_id, graph, task_results, trial_round)
        alt missing is not None
          Runner->>Runner: result = unreachable_trial_result(task_id, trial_round, missing)
          Runner->>Runner: task_results[task_id].trials.append(result)
        else no missing dependency
          Runner->>Env: execute_single_trial(task_id, trial_round, ...)
          Env->>Env: reset_state()
          Env->>Env: get_task_prompt()
          Env->>Env: configure_additional_apps()
          Env->>Env: submit_answer(answer)
          Env->>Env: score()
          Env-->>Runner: TaskTrialResult
          Runner->>Runner: task_results[task_id].trials.append(result)
        end
      end
    end
Loading

File-Level Changes

Change Details Files
Centralize runtime/task state in CorralState and remove Environment-specific TaskState/LLMMessage tracking.
  • Introduce corral.backend.state.CorralState and TaskRunState as the single mutable runtime state, including tool statistics and trial archival.
  • Refactor Environment to use CorralState for trials, messages, tool_calls, submissions, scores, hidden_args, and trial snapshots instead of TaskState/LLMMessage.
  • Update server endpoints to expose state via CorralState.snapshot and to retrieve trials/scores from CorralState.trials.
src/corral/backend/state.py
src/corral/backend/env.py
src/corral/backend/server.py
Replace TaskGroup and implicit result_from_* wiring with an explicit, immutable task graph and InputRef-based dependencies.
  • Redesign TaskDefinition as an immutable config with input_map[InputRef], hooks (prompt_fn, setup_fn, scoring_fn, resolve_answer), and excluded_tools.
  • Remove TaskGroup and add graph utilities: build_dependency_graph, assert_dependencies_selected, order_selected, topological_order, connected_components, validate_task_graph.
  • Add build_environments to construct per-task Environment instances, sharing TaskRunState across connected components instead of using TaskGroup.
src/corral/backend/task.py
src/corral/backend/env.py
src/corral/utils/__init__.py
src/corral/utils/task_group.py
Enforce correct chained execution order with dependency-closed selections and broken-chain short-circuiting in the runner.
  • Add unreachable_trial_result and _unsatisfied_dependency helpers and extend run_chained_trials to mark downstream tasks as unreachable without agent calls when dependencies fail.
  • Require dependency-closed task selections via assert_dependencies_selected and run tasks in topological order via order_selected using a dependency graph fetched from the server.
  • Add tests verifying ordering, dependency-closure enforcement, and chained short-circuit semantics for run_chained_trials.
src/corral/run.py
src/corral/router/routes.py
src/corral/backend/server.py
tests/test_task_graph.py
Unify tool configuration via Toolset and simplify Environment tool resolution and tool call recording.
  • Introduce Toolset value object with pool/common/workspace_factory/select_all_when_unspecified and default_file_tools, and update Environment to resolve tools via Toolset.resolve per trial.
  • Route hidden tool arguments through CorralState.hidden_args and record tool calls via CorralState.record_tool_call, adjusting call_tool accordingly.
  • Update tests to construct Environments with TaskDefinition and Toolset, disabling workspace tools where appropriate.
src/corral/backend/env.py
tests/test_tool_processing.py
tests/test_configure_additional_apps.py
Align LaTeX generation, server API, and router client with the new task graph and environment model.
  • Refactor Environment.to_latex to derive dependencies from TaskDefinition.input_map, use state.task_group_id for env_name, and aggregate scoring functions from group_tasks.
  • Expose /dependency_graph and compute dependency_chain using TaskDefinition.dependencies via env.group_tasks, and update Router with get_dependency_graph.
  • Adjust task prompt generation and LaTeX docs descriptions to align with input_map and the new prompt resolution timing.
src/corral/backend/env.py
src/corral/backend/server.py
src/corral/router/routes.py
src/corral/run.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@MrtinoRG, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8aaf7f3a-d418-4c41-92f1-3f03baed224b

📥 Commits

Reviewing files that changed from the base of the PR and between 332e89b and 89fdd90.

📒 Files selected for processing (1)
  • tests/backend/test_mcp_server.py
📝 Walkthrough

Walkthrough

This PR replaces the mutable TaskGroup model with immutable task definitions, explicit dependency graphs, and CorralState-backed runtime state. Environments, chained execution, server APIs, benchmark integrations, documentation, and tests are migrated to the new architecture.

Changes

Core architecture

Layer / File(s) Summary
Task contracts and dependency graph
src/corral/backend/task.py, src/corral/utils/__init__.py
Adds InputRef, frozen TaskDefinition, graph validation and ordering utilities, connected components, scoring adapters, and removes the task_group export.
Runtime state and environment construction
src/corral/backend/state.py, src/corral/backend/env.py
Adds CorralState and TaskRunState; converts Environment to a concrete task-driven implementation with Toolset resolution, state-backed lifecycle, scoring, prompts, snapshots, and build_environments().
Chained execution and APIs
src/corral/run.py, src/corral/backend/server.py, src/corral/router/routes.py
Enforces dependency-closed ordering, records unreachable downstream trials, exposes dependency graphs, and migrates server state and trial endpoints to CorralState.
Benchmark migrations and validation
tasks/*/env.py, tasks/catalyst/tests/*, tests/test_task_graph.py, tests/test_tool_processing.py, tests/test_configure_additional_apps.py
Migrates benchmark task wiring and environment factories to input_map, Toolset, hooks, and shared state, with updated integration and dependency-graph tests.
Documentation and removals
docs/*, tasks/samplemath/README.md, tasks/samplemath/samplemath/env.py
Documents linked environments and CorralState, updates task-chaining examples, and removes the standalone samplemath environment.

Estimated code review effort: 4 (Complex) | ~75 minutes

Suggested reviewers: kjappelbaum, n0w0f

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.42% 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 matches the main refactor themes: unified runtime state, TaskGroup removal/replacement, and the new Toolset API.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new_state

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.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In CorralState.start_new_trial, only trials in states {submitted, surrendered, scored, finalized} get archived into trials; if a trial is started but never reaches one of these states (e.g. agent / tool crash before submit), calling reset_state will drop that history — consider explicitly archiving any prior trial once it has been running for robustness/debuggability.
  • In Toolset.resolve, missing tools from the pool are logged and silently skipped; if a task declares a tool that’s not available this is likely a configuration error rather than a soft warning, so consider raising (or optionally toggling between strict/lenient modes) to fail fast on misconfigured tool names.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CorralState.start_new_trial`, only trials in states {submitted, surrendered, scored, finalized} get archived into `trials`; if a trial is started but never reaches one of these states (e.g. agent / tool crash before submit), calling `reset_state` will drop that history — consider explicitly archiving any prior trial once it has been `running` for robustness/debuggability.
- In `Toolset.resolve`, missing tools from the pool are logged and silently skipped; if a task declares a tool that’s not available this is likely a configuration error rather than a soft warning, so consider raising (or optionally toggling between strict/lenient modes) to fail fast on misconfigured tool names.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors Corral’s runtime execution model to centralize mutable run state in a new CorralState, replaces mutable task-group runtime behavior with an explicit dependency graph API (including topological ordering and dependency-closure checks), and simplifies tool configuration via a single Toolset object.

Changes:

  • Introduces CorralState / TaskRunState as the unified mutable runtime state and updates server/state APIs to return serialized snapshots.
  • Replaces implicit/chained task-group execution with explicit dependency graph utilities (assert_dependencies_selected, order_selected, topological_order) and adds broken-chain short-circuiting in chained runs.
  • Consolidates tool configuration into Toolset (pool/common/workspace tool factory) and updates environment + tests to use the new constructor/API.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_tool_processing.py Updates environment test scaffolding to the new Environment(..., task, toolset=...) constructor and Toolset usage.
tests/test_task_graph.py Adds coverage for dependency closure, ordering, and chained short-circuit behavior.
tests/test_configure_additional_apps.py Updates DummyEnv construction to pass a TaskDefinition.
src/corral/utils/task_group.py Removes the old mutable task-group runtime environment implementation.
src/corral/utils/init.py Drops task_group exports to match removal.
src/corral/run.py Enforces dependency-closed selection + topological order in chained mode; adds “unreachable” short-circuit results and dependency checks per round.
src/corral/router/routes.py Adds router support for fetching the dependency graph from the server.
src/corral/backend/task.py Introduces TaskDefinition.input_map + InputRef, graph utilities, and dependency-closure + ordering helpers.
src/corral/backend/state.py Adds new unified mutable runtime state (CorralState) and snapshot/tool-statistics serialization.
src/corral/backend/server.py Adds /dependency_graph endpoint and switches state responses to serialized snapshots.
src/corral/backend/env.py Implements Toolset, rewires environment runtime state to CorralState, adds graph-derived environment building, and updates scoring/tool execution paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
missing = {tid: deps for tid, deps in missing.items() if deps}
if missing:
lines = "; ".join(f"{t} needs {d}" for t, d in missing.items())
Comment thread src/corral/run.py
Comment on lines +345 to +349
missing = _unsatisfied_dependency(task_id, graph, task_results, trial_round)
if missing is not None:
logger.warning(
f"Skipping task {task_id}: dependency {missing!r} did not "
f"produce a usable output this round (chain broken upstream)."

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/corral/backend/env.py (2)

675-680: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse string verbosity values instead of forcing FULL.

On Lines 677-680, valid string inputs like "brief" and "detailed" are ignored and coerced to ToolVerbosity.FULL. This silently breaks caller intent.

💡 Suggested fix
         if verbosity is None:
             resolved_verbosity = ToolVerbosity.DETAILED
         elif isinstance(verbosity, ToolVerbosity):
             resolved_verbosity = verbosity
+        elif isinstance(verbosity, str):
+            try:
+                resolved_verbosity = ToolVerbosity(verbosity.lower())
+            except ValueError:
+                logger.warning(
+                    f"Unknown verbosity {verbosity!r}; defaulting to FULL."
+                )
+                resolved_verbosity = ToolVerbosity.FULL
         else:
             resolved_verbosity = ToolVerbosity.FULL
🤖 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 `@src/corral/backend/env.py` around lines 675 - 680, The current branch that
sets resolved_verbosity forces any non-ToolVerbosity value to ToolVerbosity.FULL
and ignores valid string inputs; update the logic in the resolver (the code that
assigns resolved_verbosity) to detect str inputs and map them to the enum (e.g.,
normalize with verbosity.lower() or .upper() and look up the corresponding
ToolVerbosity member), handling invalid strings by falling back to a safe
default (e.g., ToolVerbosity.FULL) or raising a clear error; specifically change
the else branch so that if isinstance(verbosity, str) you convert/lookup to
ToolVerbosity (with a try/except for unknown names) instead of unconditionally
assigning ToolVerbosity.FULL.

456-464: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle missing hidden args without raising runtime exceptions.

On Line 462, raising KeyError aborts call_tool() and skips failed-call recording. This breaks the method’s error-handling contract and can crash execution for configuration mistakes.

💡 Suggested fix
         if hasattr(tool, "hidden_args") and tool.hidden_args:
             # tool.hidden_args is a list of argument names to hide
             for hidden_arg in tool.hidden_args:
                 if hidden_arg in self.state.hidden_args:
                     call_args[hidden_arg] = self.state.hidden_args[hidden_arg]
                 else:
-                    raise KeyError(
-                        f"Hidden argument '{hidden_arg}' required by tool '{tool_name}' not found in environment's hidden_args."
-                    )
+                    duration = time.perf_counter() - start_time
+                    tool_call = ToolCall(
+                        tool_name=tool_name,
+                        arguments=original_arguments,
+                        result=None,
+                        status=ToolCallStatus.INVALID_ARGS,
+                        error_message=(
+                            f"Hidden argument '{hidden_arg}' required by tool "
+                            f"'{tool_name}' not found in environment hidden_args."
+                        ),
+                        duration=duration,
+                    )
+                    self.state.record_tool_call(tool_call)
+                    return tool_call
🤖 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 `@src/corral/backend/env.py` around lines 456 - 464, The code currently raises
a KeyError when a required hidden arg is missing which aborts call_tool() and
prevents the normal failed-call recording; instead, do not raise—construct a
clear error message, call the existing failure-reporting path used by
call_tool() (e.g., invoke the method that records failed tool calls such as
record_failed_call or the call_tool() error-return path) with that message, log
a warning via the environment logger, and return an error result (for example
{'success': False, 'error': error_msg}) so execution continues and the failure
is recorded; update the block handling tool.hidden_args (the loop over
hidden_arg, self.state.hidden_args and call_args) to implement this non-throwing
failure flow.
🤖 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 `@src/corral/router/routes.py`:
- Around line 69-73: get_dependency_graph currently calls requests.get without a
timeout and can hang; update the requests.get call in get_dependency_graph to
pass a timeout (e.g. timeout=10) so the HTTP request fails fast on network
stalls. If you prefer configurability, add an instance attribute like
self.http_timeout (set in __init__) and use
requests.get(f"{self.base_url}/dependency_graph", timeout=self.http_timeout)
instead; keep the response.raise_for_status() and return response.json() as-is.

---

Outside diff comments:
In `@src/corral/backend/env.py`:
- Around line 675-680: The current branch that sets resolved_verbosity forces
any non-ToolVerbosity value to ToolVerbosity.FULL and ignores valid string
inputs; update the logic in the resolver (the code that assigns
resolved_verbosity) to detect str inputs and map them to the enum (e.g.,
normalize with verbosity.lower() or .upper() and look up the corresponding
ToolVerbosity member), handling invalid strings by falling back to a safe
default (e.g., ToolVerbosity.FULL) or raising a clear error; specifically change
the else branch so that if isinstance(verbosity, str) you convert/lookup to
ToolVerbosity (with a try/except for unknown names) instead of unconditionally
assigning ToolVerbosity.FULL.
- Around line 456-464: The code currently raises a KeyError when a required
hidden arg is missing which aborts call_tool() and prevents the normal
failed-call recording; instead, do not raise—construct a clear error message,
call the existing failure-reporting path used by call_tool() (e.g., invoke the
method that records failed tool calls such as record_failed_call or the
call_tool() error-return path) with that message, log a warning via the
environment logger, and return an error result (for example {'success': False,
'error': error_msg}) so execution continues and the failure is recorded; update
the block handling tool.hidden_args (the loop over hidden_arg,
self.state.hidden_args and call_args) to implement this non-throwing failure
flow.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26e9d569-d2d2-4b6e-93a0-1a5200dc4c33

📥 Commits

Reviewing files that changed from the base of the PR and between 88d9b67 and 102d78c.

📒 Files selected for processing (11)
  • src/corral/backend/env.py
  • src/corral/backend/server.py
  • src/corral/backend/state.py
  • src/corral/backend/task.py
  • src/corral/router/routes.py
  • src/corral/run.py
  • src/corral/utils/__init__.py
  • src/corral/utils/task_group.py
  • tests/test_configure_additional_apps.py
  • tests/test_task_graph.py
  • tests/test_tool_processing.py
💤 Files with no reviewable changes (2)
  • src/corral/utils/task_group.py
  • src/corral/utils/init.py

Comment on lines +69 to +73
def get_dependency_graph(self) -> dict[str, list[str]]:
"""Fetch the task dependency graph as a ``{task_id: [deps]}`` dict."""
response = requests.get(f"{self.base_url}/dependency_graph")
response.raise_for_status()
return response.json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify timeout usage for dependency graph call and nearby router GET requests.
rg -n -C2 'def get_dependency_graph|requests\.get\(' src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 2684


🏁 Script executed:

#!/bin/bash
# Check whether any requests in this file already use timeouts.
rg -n "timeout\s*=" src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 144


🏁 Script executed:

#!/bin/bash
# Inspect around the one timeout usage and locate how `timeout` is defined/passed.
sed -n '140,220p' src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 3115


🏁 Script executed:

#!/bin/bash
# Find all requests.get calls that already include timeout= in this file.
rg -n "requests\.get\([^)]*timeout\s*=" src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 44


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find call sites for get_dependency_graph
rg -n "get_dependency_graph\(" -S . --glob '!**/node_modules/**' --glob '!**/.venv/**'

Repository: lamalab-org/corral

Length of output: 307


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find other usage of dependency graph endpoint string
rg -n "/dependency_graph" -S src

Repository: lamalab-org/corral

Length of output: 228


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find how dependency chaining is handled (control path hints)
rg -n "dependency_chain|get_dependency_graph" src/corral -S

Repository: lamalab-org/corral

Length of output: 976


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find call sites for get_dependency_graph
rg -n "get_dependency_graph\(" -S . --glob '!**/node_modules/**' --glob '!**/.venv/**'

Repository: lamalab-org/corral

Length of output: 307


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find other usage of dependency graph endpoint string
rg -n "/dependency_graph" -S src

Repository: lamalab-org/corral

Length of output: 228


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find how dependency chaining is handled (control path hints)
rg -n "dependency_chain|get_dependency_graph" src/corral -S

Repository: lamalab-org/corral

Length of output: 976


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '760,830p' src/corral/run.py

Repository: lamalab-org/corral

Length of output: 2814


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 5588


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "requests\.Session|timeout\s*[:=]" src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 280


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "\bget_dependency_graph\(" -S src/corral/run.py

Repository: lamalab-org/corral

Length of output: 128


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '760,830p' src/corral/run.py

Repository: lamalab-org/corral

Length of output: 2814


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 5588


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "requests\.Session|timeout\s*[:=]" src/corral/router/routes.py

Repository: lamalab-org/corral

Length of output: 280


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "\bget_dependency_graph\(" -S src/corral/run.py

Repository: lamalab-org/corral

Length of output: 128


Add an HTTP timeout to dependency-graph fetch

In src/corral/router/routes.py, get_dependency_graph() calls requests.get(.../dependency_graph) without a timeout; src/corral/run.py uses this on the dependency-chained execution path before run_chained_trials, so a stalled connection can block the run indefinitely.

💡 Suggested fix
 def get_dependency_graph(self) -> dict[str, list[str]]:
     """Fetch the task dependency graph as a ``{task_id: [deps]}`` dict."""
-    response = requests.get(f"{self.base_url}/dependency_graph")
+    response = requests.get(f"{self.base_url}/dependency_graph", timeout=30.0)
     response.raise_for_status()
     return response.json()
🧰 Tools
🪛 ast-grep (0.43.0)

[info] 70-70: no timeout was given on call to external resource
Context: requests.get(f"{self.base_url}/dependency_graph")
Note: [CWE-1088].

(requests-timeout)

🪛 Ruff (0.15.15)

[error] 71-71: Probable use of requests call without timeout

(S113)

🤖 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 `@src/corral/router/routes.py` around lines 69 - 73, get_dependency_graph
currently calls requests.get without a timeout and can hang; update the
requests.get call in get_dependency_graph to pass a timeout (e.g. timeout=10) so
the HTTP request fails fast on network stalls. If you prefer configurability,
add an instance attribute like self.http_timeout (set in __init__) and use
requests.get(f"{self.base_url}/dependency_graph", timeout=self.http_timeout)
instead; keep the response.raise_for_status() and return response.json() as-is.

Source: Linters/SAST tools

@kjappelbaum

Copy link
Copy Markdown
Contributor

Sorry for the delay. I just now reached this part of my inbox and will try to look at it tomorrow.

MrtinoRG and others added 3 commits July 13, 2026 10:57
Preserve functionality from both branches:
- main: new black-box agents (Codex, Claude Code, Terminus, OpenHands),
  task-scoped MCP server, AgentRunResult return type, cumulative token usage.
- new_state: reworked backend Environment/task-graph (state.py, task.py),
  bench_from_traces / _run_benchmark orchestration in run.py.

Conflict resolution:
- run.py, routes.py, reflexion_agent.py, base_agent.py: docstring-only
  conflicts (backtick formatting) — kept the backticked wording; code
  merged automatically (execute_single_trial consumes AgentRunResult).
- uv.lock: regenerated from the merged pyproject via `uv lock`.
- tests/backend/test_mcp_server.py: adapted DummyEnv to new_state's
  Environment(task_id, task, ...) signature (added required TaskDefinition),
  so main's MCP tests run against the merged API.

Verified: 426 passed. Remaining test_metrics_registry parallel failures are
a pre-existing local-venv multiprocessing-spawn artifact (byte-identical to
both parents, unrelated to the merge). Agent SDK tests (codex/claude_code/
openhands) require optional extras not installed here.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tasks/wetlab/wetlab/env.py (1)

299-331: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Thread a work directory through WetLab env creation create_qualysis_environments() builds every environment with the default empty base_work_dir, so all tasks end up reading and writing the same compositions.pkl in the current directory. Pass a per-run work_dir into build_environments(...) here, like the other benchmark entrypoints.

🤖 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 `@tasks/wetlab/wetlab/env.py` around lines 299 - 331, Update
create_qualysis_environments to accept a per-run work_dir parameter and pass it
through to build_environments, ensuring each WetLab run uses its own directory
for compositions.pkl instead of the default current directory.
🧹 Nitpick comments (1)
src/corral/router/routes.py (1)

86-101: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add HTTP timeout to get_mcp_tool_schema.

The new get_mcp_tool_schema method calls requests.get(...) without a timeout, consistent with the pre-existing pattern in this file but introducing the same hang risk. Consider adding a timeout here (and consolidating across the file via a shared self.http_timeout attribute or a requests.Session with default timeouts).

♻️ Proposed fix
 def get_mcp_tool_schema(
     self, task_id: str, verbosity: str | None = None
 ) -> dict[str, Any]:
     """Get the task's tools in MCP representation plus their schema digest."""
     verbosity = verbosity or self.current_verbosity
     params = {"verbosity": verbosity}
     response = requests.get(
-        f"{self.base_url}/tasks/{task_id}/tools/mcp", params=params
+        f"{self.base_url}/tasks/{task_id}/tools/mcp", params=params, timeout=30.0
     )
     response.raise_for_status()
     return response.json()
🤖 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 `@src/corral/router/routes.py` around lines 86 - 101, Update
get_mcp_tool_schema to pass the established HTTP timeout when calling
requests.get, reusing a shared self.http_timeout or the file’s existing timeout
configuration rather than leaving the request unbounded. Preserve the current
URL, parameters, response validation, and JSON return behavior.

Source: Linters/SAST tools

🤖 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 `@docs/index.md`:
- Around line 136-141: Update the build_environments example to pass my_tools
using the toolset keyword instead of available_tools, matching the parameter
accepted by Environment.__init__.
- Around line 143-144: Update the documentation example around task_input so
resolve_inputs is called only after the upstream analyze_structure task
completes; alternatively, demonstrate it using already-resolved shared state. Do
not imply that build_environments(...) populates retrieve_data before task
execution.

In `@tasks/catalyst/src/catalyst/env.py`:
- Around line 103-104: Update the environment-building function containing the
name parameter so its default is unique per configuration rather than the fixed
"catalyst" label. Derive the label from the available task_type and level
values, or make name required and update callers accordingly, ensuring trace and
LaTeX metadata remains distinguishable.

---

Outside diff comments:
In `@tasks/wetlab/wetlab/env.py`:
- Around line 299-331: Update create_qualysis_environments to accept a per-run
work_dir parameter and pass it through to build_environments, ensuring each
WetLab run uses its own directory for compositions.pkl instead of the default
current directory.

---

Nitpick comments:
In `@src/corral/router/routes.py`:
- Around line 86-101: Update get_mcp_tool_schema to pass the established HTTP
timeout when calling requests.get, reusing a shared self.http_timeout or the
file’s existing timeout configuration rather than leaving the request unbounded.
Preserve the current URL, parameters, response validation, and JSON return
behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13c8411f-93b8-41d6-943f-10398cb0d25a

📥 Commits

Reviewing files that changed from the base of the PR and between 102d78c and 332e89b.

⛔ Files ignored due to path filters (1)
  • tasks/catalyst/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • docs/documentation/explanation.md
  • docs/documentation/how_tos/task_chaining.md
  • docs/documentation/reference.md
  • docs/index.md
  • src/corral/agents/reflexion_agent.py
  • src/corral/backend/env.py
  • src/corral/backend/server.py
  • src/corral/backend/state.py
  • src/corral/backend/task.py
  • src/corral/router/routes.py
  • src/corral/run.py
  • tasks/afm/src/env.py
  • tasks/catalyst/src/catalyst/env.py
  • tasks/catalyst/tests/test_environment_integration.py
  • tasks/corral_md/src/corral_md/env.py
  • tasks/ml/src/ml/env.py
  • tasks/resistor_network/src/resistor_network/env.py
  • tasks/retrosynthesis/retrosynthesis/env.py
  • tasks/samplemath/README.md
  • tasks/samplemath/samplemath/env.py
  • tasks/samplemath/samplemath/env_subtask.py
  • tasks/spectra_elucidation/spectra_elucidation/env.py
  • tasks/wetlab/wetlab/env.py
  • tests/backend/test_mcp_server.py
  • tests/test_task_graph.py
💤 Files with no reviewable changes (1)
  • tasks/samplemath/samplemath/env.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/corral/run.py
  • tests/test_task_graph.py
  • src/corral/backend/state.py
  • src/corral/backend/task.py
  • src/corral/backend/env.py

Comment thread docs/index.md
Comment on lines +136 to 141
environments = build_environments(
{"retrieve_data": task1, "analyze_structure": task2},
base_work_dir="workdir",
name="molecular_workflow",
available_tools=my_tools,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'def build_environments' -A 30 src/corral/backend/env.py

Repository: lamalab-org/corral

Length of output: 1762


🏁 Script executed:

#!/bin/bash
sed -n '128,150p' docs/index.md

Repository: lamalab-org/corral

Length of output: 985


🏁 Script executed:

#!/bin/bash
sed -n '835,930p' src/corral/backend/env.py

Repository: lamalab-org/corral

Length of output: 1338


🏁 Script executed:

#!/bin/bash
rg -nP 'class Environment|def __init__' src/corral/backend/env.py -A 40

Repository: lamalab-org/corral

Length of output: 2831


Use toolset= here, not available_tools=. build_environments(..., **env_kwargs) forwards extra keywords to Environment.__init__, which only accepts toolset; this example will fail if copied as written.

🤖 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 `@docs/index.md` around lines 136 - 141, Update the build_environments example
to pass my_tools using the toolset keyword instead of available_tools, matching
the parameter accepted by Environment.__init__.

Comment thread docs/index.md
Comment on lines +143 to +144
# Access input from previous task (resolved from the shared state)
task_input = environments["analyze_structure"].state.resolve_inputs(task2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '120,170p' docs/index.md && printf '\n---\n' && rg -n "def resolve_inputs|resolve_inputs\(" -S .

Repository: lamalab-org/corral

Length of output: 2641


🏁 Script executed:

sed -n '150,240p' src/corral/backend/state.py && printf '\n---\n' && sed -n '200,270p' src/corral/backend/env.py && printf '\n---\n' && sed -n '135,150p' docs/index.md

Repository: lamalab-org/corral

Length of output: 7192


🏁 Script executed:

rg -n "def build_environments|build_environments\(" src docs -S && printf '\n---\n' && sed -n '1,220p' src/corral/backend/env.py

Repository: lamalab-org/corral

Length of output: 9520


🏁 Script executed:

sed -n '805,940p' src/corral/backend/env.py && printf '\n---\n' && sed -n '1,220p' src/corral/backend/task.py

Repository: lamalab-org/corral

Length of output: 10253


Don’t call resolve_inputs before the upstream task has run. build_environments(...) only shares state; it does not populate retrieve_data’s output, so this example will raise RuntimeError as written. Move it after the first task completes or show a resolved state instead.

🤖 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 `@docs/index.md` around lines 143 - 144, Update the documentation example
around task_input so resolve_inputs is called only after the upstream
analyze_structure task completes; alternatively, demonstrate it using
already-resolved shared state. Do not imply that build_environments(...)
populates retrieve_data before task execution.

Comment on lines +103 to +104
name: str = "catalyst",
) -> dict[str, Environment]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files and inspect the catalyst env definition plus downstream consumers.
git ls-files | rg 'tasks/catalyst/src/catalyst/(env\.py|.*\.py)$|build_environments|dependency|graph|trace|latex' || true

printf '\n--- env.py outline ---\n'
ast-grep outline tasks/catalyst/src/catalyst/env.py --view expanded || true

printf '\n--- env.py excerpt ---\n'
nl -ba tasks/catalyst/src/catalyst/env.py | sed -n '1,220p'

printf '\n--- search for name usage in catalyst task ---\n'
rg -n "\bname\b" tasks/catalyst/src/catalyst -g '*.py' || true

printf '\n--- search for build_environments symbol across repo ---\n'
rg -n "def build_environments|build_environments\(" . -g '*.py' || true

Repository: lamalab-org/corral

Length of output: 1518


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- env.py create_environments section ---\n'
sed -n '95,170p' tasks/catalyst/src/catalyst/env.py

printf '\n--- env.py remainder around parser/main ---\n'
sed -n '170,240p' tasks/catalyst/src/catalyst/env.py

printf '\n--- name usage within catalyst package ---\n'
rg -n "\bname\b" tasks/catalyst/src/catalyst -g '*.py' || true

printf '\n--- create_environments call sites ---\n'
rg -n "create_environments\(" . -g '*.py' || true

Repository: lamalab-org/corral

Length of output: 8528


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- build_environments definition search ---\n'
rg -n "def build_environments|class .*build_environments|build_environments\(" . -g '*.py' || true

printf '\n--- task graph / trace / latex name usage search ---\n'
rg -n "\bname\b|base_name|label|trace|latex" src tasks -g '*.py' | sed -n '1,220p' || true

Repository: lamalab-org/corral

Length of output: 21749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- backend env.py build_environments section ---\n'
sed -n '760,940p' src/corral/backend/env.py

printf '\n--- backend task.py ---\n'
sed -n '1,220p' src/corral/backend/task.py

printf '\n--- search for environment name propagation ---\n'
rg -n "\bname\b|base_name|benchmark|trace|latex|group" src/corral/backend -g '*.py' || true

Repository: lamalab-org/corral

Length of output: 20920


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- env.py latex section ---\n'
sed -n '640,790p' src/corral/backend/env.py

printf '\n--- task_group_id search ---\n'
rg -n "\btask_group_id\b" . -g '*.py' || true

Repository: lamalab-org/corral

Length of output: 6586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- component_id search ---\n'
rg -n "\bcomponent_id\b" . -g '*.py' || true

printf '\n--- task_group_id search ---\n'
rg -n "\btask_group_id\b" . -g '*.py' || true

Repository: lamalab-org/corral

Length of output: 870


name should default to a per-config label
name="catalyst" makes trace/LaTeX metadata ambiguous when multiple catalyst configs are created in one run. Derive a default from task_type/level, or require callers to pass one.

🤖 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 `@tasks/catalyst/src/catalyst/env.py` around lines 103 - 104, Update the
environment-building function containing the name parameter so its default is
unique per configuration rather than the fixed "catalyst" label. Derive the
label from the available task_type and level values, or make name required and
update callers accordingly, ensuring trace and LaTeX metadata remains
distinguishable.

@MrtinoRG
MrtinoRG merged commit 33fa262 into main Jul 13, 2026
19 checks passed
@MrtinoRG
MrtinoRG deleted the new_state branch July 13, 2026 11:12
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.

In subtasks, when error in chained tasks pass "No Answer" instead of the error Rethink state

3 participants