Skip to content

feat(core): per-state concurrency limits for batch execution - #440

Merged
frankbria merged 2 commits into
mainfrom
feature/issue-407-per-state-concurrency
Mar 13, 2026
Merged

feat(core): per-state concurrency limits for batch execution#440
frankbria merged 2 commits into
mainfrom
feature/issue-407-per-state-concurrency

Conversation

@frankbria

@frankbria frankbria commented Mar 13, 2026

Copy link
Copy Markdown
Owner

Summary

Implements #407: Per-State Concurrency Limits for Batch Execution

Adds per-status parallelism caps to batch execution, allowing different concurrency limits for tasks in different states (e.g., allow 3 READY tasks but only 1 IN_PROGRESS).

  • ConcurrencyConfig dataclass with max_parallel, by_status, get_limit_for_status(), and effective_workers() computing min of global/per-status/group-size limits
  • parse_concurrency_by_status() for CLI string parsing ("READY=3,IN_PROGRESS=2")
  • BatchRun.concurrency field threaded through start_batch()
  • BatchConfig in EnvironmentConfig for YAML-based config persistence

Acceptance Criteria

  • Per-status limits respected during parallel batch execution
  • Global limit still enforced as upper bound
  • Config file loading works (BatchConfig in config.yaml)
  • Status counts updated correctly as tasks transition
  • Unspecified statuses fall back to global limit
  • Unit tests for slot calculation with mixed status limits
  • Parse validation for CLI flag format

Test Plan

  • 21 unit tests covering ConcurrencyConfig, BatchConfig, parsing, and start_batch
  • All core tests passing (regression check in progress)
  • Ruff linting clean

Implementation Notes

  • Simplified group dispatch: Rather than partitioning groups by status with sub-pools, we compute the minimum per-status limit across all statuses in the group. This is simpler and correct — the bottleneck status dictates the group's parallelism.
  • CLI --max-parallel-by-status flag: Not wired in this PR since the infrastructure is ready; can be added in a follow-up. concurrency_by_status param is available on start_batch().

Closes #407

Summary by CodeRabbit

  • New Features

    • Added status-based concurrency controls to regulate parallel task execution and compute effective worker counts
    • New batch configuration options to set global and per-status concurrency limits and persist them with batch runs
    • CLI parsing support for per-status concurrency input
  • Tests

    • Comprehensive test coverage for concurrency behavior, parsing, configuration, and batch propagation

Add ConcurrencyConfig with per-status parallelism caps, allowing different
concurrency limits for tasks in different states during batch execution.

- ConcurrencyConfig dataclass with max_parallel, by_status, get_limit_for_status(),
  effective_workers() computing min of global/per-status/group-size limits
- parse_concurrency_by_status() for CLI string parsing ("READY=3,IN_PROGRESS=2")
- BatchRun.concurrency field threaded through start_batch()
- BatchConfig in EnvironmentConfig for config.yaml persistence
- 21 unit tests covering config, slot calculation, parsing, and start_batch integration

Closes #407
@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb357b4c-e849-410e-a980-7b7004e79083

📥 Commits

Reviewing files that changed from the base of the PR and between 06dc168 and 18db151.

📒 Files selected for processing (1)
  • codeframe/core/conductor.py

Walkthrough

Adds per-state concurrency controls: a new ConcurrencyConfig dataclass, parsing helper, and plumbing so batch runs can carry and apply per-status concurrency limits; config support added to EnvironmentConfig; comprehensive unit tests added for the new behavior. (49 words)

Changes

Cohort / File(s) Summary
Core Conductor
codeframe/core/conductor.py
Adds ConcurrencyConfig dataclass and parse_concurrency_by_status parser; extends BatchRun with a concurrency field; start_batch accepts concurrency_by_status and injects ConcurrencyConfig; parallel execution path updated to compute effective workers using per-status limits when provided.
Configuration
codeframe/core/config.py
Introduces BatchConfig and adds a batch field to EnvironmentConfig; EnvironmentConfig.from_dict updated to parse batch config including per-status settings.
Tests
tests/core/test_concurrency_config.py
New test module covering ConcurrencyConfig behavior, effective worker calculations, BatchConfig parsing/serialization, start_batch concurrency propagation, and parse_concurrency_by_status parsing and error cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I tally states and count each hop,
READY bounds set, IN_PROGRESS on stop.
Config tucked in the batch I bear,
Workers dance with careful care.
Tests applaud — a carrot-flavored cheer! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 'feat(core): per-state concurrency limits for batch execution' clearly and accurately summarizes the main change: implementing per-state concurrency limits functionality.
Linked Issues check ✅ Passed The PR implements the core coding requirements from #407: ConcurrencyConfig dataclass with max_parallel and by_status fields, parse_concurrency_by_status function, effective_workers calculation, BatchConfig in EnvironmentConfig, and integration into start_batch and _execute_parallel with comprehensive unit tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #407 requirements: ConcurrencyConfig, BatchConfig, parsing logic, and batch execution integration. The CLI flag --max-parallel-by-status is noted as not wired per PR objectives, which is acceptable as out-of-scope.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-407-per-state-concurrency
📝 Coding Plan
  • Generate coding plan for human review comments

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

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Code Review — Per-State Concurrency Limits (#407)

Good infrastructure work overall. The dataclasses are clean, the test suite is solid, and the config serialization roundtrip is handled correctly. A few issues to address before this is functionally complete:


Critical: effective_workers() is never called in _execute_parallel()

This is the core gap in the PR. The new ConcurrencyConfig is stored on BatchRun.concurrency but _execute_parallel() still uses batch.max_parallel directly at line 1525:

effective_workers = min(group_size, batch.max_parallel)

Without calling batch.concurrency.effective_workers() here, per-status limits have no effect at runtime. The unit test for start_batch only validates that batch.concurrency.by_status is set correctly — it does not test that limits are enforced during execution.

Similarly, line 1480 still prints batch.max_parallel rather than the effective limit.


Config/runtime disconnect: BatchConfig values are never used

BatchConfig is loaded from config.yaml into EnvironmentConfig.batch, but nothing reads those values when calling start_batch(). The config fields are dead code until the CLI or caller bridges them by reading env_cfg.batch.max_parallel_by_status and passing it as concurrency_by_status.

Also note the field naming inconsistency: BatchConfig.max_parallel_by_status vs ConcurrencyConfig.by_status. Worth aligning these before they diverge further.


Input validation gap in parse_concurrency_by_status()

Non-positive values are silently accepted (READY=0, READY=-1). A guard after int(val.strip()) should raise ValueError for values less than 1. A test for this case is also missing.


ConcurrencyConfig persistence in BatchRun

BatchRun is persisted to SQLite. The DB serialization/deserialization code around line 1974 does not appear to handle the new concurrency field — it is absent from the INSERT and SELECT column lists. Either serialize it (e.g. as JSON) or document that it is ephemeral and reconstruct it on load from max_parallel.


Minor

  • The docstring at line 1465 ("up to max_parallel") should be updated once dispatch is wired to ConcurrencyConfig.
  • The global_running parameter in effective_workers is always 0 in the current (unwired) usage — ensure it tracks actual in-flight tasks when the real integration lands.

Test coverage

The 21 unit tests are well-structured and cover the interesting edge cases (mixed statuses, global_running overflow, None/empty parsing). Once _execute_parallel is wired up, an integration-level test that dispatches real tasks and asserts per-status limits are respected would complete the picture.

Overall this is solid groundwork — the main ask is closing the loop between the infrastructure layer and the actual execution path before merging.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/core/test_concurrency_config.py (2)

123-147: Test validates integration but not actual enforcement.

This test correctly verifies that start_batch accepts concurrency_by_status and stores it in the batch. However, since _execute_serial is mocked, it doesn't verify that the limits are actually enforced during execution.

Given that effective_workers() isn't currently used in the execution path (see conductor.py review), consider adding an integration test that verifies per-status limits are respected when execution is complete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_concurrency_config.py` around lines 123 - 147, The test
TestStartBatchConcurrency only asserts that start_batch stores
concurrency_by_status but doesn't exercise enforcement because _execute_serial
is mocked; update or add an integration-style test that does not mock
_execute_serial (and instead lets conductor.start_batch perform real execution)
to verify that per-status limits are enforced at runtime by exercising
effective_workers() in the execution path—specifically call start_batch with
concurrency_by_status={"READY":2} and real task mocks that transition statuses
so you can assert no more than 2 tasks run concurrently in READY state and
overall behavior (check batch.concurrency.by_status and runtime concurrency
counts) after execution completes; reference start_batch, _execute_serial, and
effective_workers to find where to remove the mock and assert enforcement.

176-186: Consider adding a test for non-integer value parsing.

The tests cover invalid status names and invalid format (: instead of =), but don't cover the case where the value isn't a valid integer.

Suggested additional test case
def test_parse_invalid_value_raises(self) -> None:
    from codeframe.core.conductor import parse_concurrency_by_status

    with pytest.raises(ValueError):
        parse_concurrency_by_status("READY=abc")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_concurrency_config.py` around lines 176 - 186, Add a test to
assert parse_concurrency_by_status raises on non-integer values: create a new
test (e.g., test_parse_invalid_value_raises) that calls
codeframe.core.conductor.parse_concurrency_by_status with a string like
"READY=abc" and wraps it in pytest.raises(ValueError); this complements existing
tests for invalid status names and formats by verifying value parsing errors for
parse_concurrency_by_status.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/core/conductor.py`:
- Line 532: The new concurrency: ConcurrencyConfig field on BatchRun is never
persisted or restored, so update persistence to serialize and deserialize it:
modify _save_batch to include the batch.concurrency (e.g., as a JSON/text column
or separate columns) when writing the batch row, and modify _row_to_batch to
parse that stored value back into a ConcurrencyConfig instance and assign it to
BatchRun.concurrency; ensure get_batch() and resume_batch() will therefore
return the actual saved ConcurrencyConfig rather than the default. Use the
existing BatchRun, ConcurrencyConfig, _save_batch, _row_to_batch and
resume_batch symbols to locate the changes.
- Around line 455-481: The parse_concurrency_by_status function currently lets
int(val.strip()) raise an opaque ValueError for non-integer values (e.g.,
"READY=abc"); update the parsing loop in parse_concurrency_by_status to wrap the
conversion in a try/except, catch ValueError from int(), and raise a new
ValueError that clearly states which STATUS and value failed to parse (include
the raw val and the STATUS key), e.g. "Invalid concurrency for READY: 'abc'
(must be an integer)"; keep the rest of the validation (status membership)
unchanged.
- Around line 417-452: The parallel executor is ignoring batch.concurrency; in
_execute_parallel replace the simple effective_workers = min(group_size,
batch.max_parallel) with a call to batch.concurrency.effective_workers(...) so
per-status limits are applied: gather the list of statuses for the tasks in the
current group (e.g., [t.status for t in group]), pass group_size and the current
global_running count into batch.concurrency.effective_workers(statuses=statuses,
group_size=group_size, global_running=global_running), and use that result (with
a safe fallback to batch.max_parallel if batch.concurrency is None) as the
effective worker count.

---

Nitpick comments:
In `@tests/core/test_concurrency_config.py`:
- Around line 123-147: The test TestStartBatchConcurrency only asserts that
start_batch stores concurrency_by_status but doesn't exercise enforcement
because _execute_serial is mocked; update or add an integration-style test that
does not mock _execute_serial (and instead lets conductor.start_batch perform
real execution) to verify that per-status limits are enforced at runtime by
exercising effective_workers() in the execution path—specifically call
start_batch with concurrency_by_status={"READY":2} and real task mocks that
transition statuses so you can assert no more than 2 tasks run concurrently in
READY state and overall behavior (check batch.concurrency.by_status and runtime
concurrency counts) after execution completes; reference start_batch,
_execute_serial, and effective_workers to find where to remove the mock and
assert enforcement.
- Around line 176-186: Add a test to assert parse_concurrency_by_status raises
on non-integer values: create a new test (e.g., test_parse_invalid_value_raises)
that calls codeframe.core.conductor.parse_concurrency_by_status with a string
like "READY=abc" and wraps it in pytest.raises(ValueError); this complements
existing tests for invalid status names and formats by verifying value parsing
errors for parse_concurrency_by_status.
🪄 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: 4937c353-84c0-4a3a-b870-f074276efffc

📥 Commits

Reviewing files that changed from the base of the PR and between 249e2c4 and 06dc168.

📒 Files selected for processing (3)
  • codeframe/core/conductor.py
  • codeframe/core/config.py
  • tests/core/test_concurrency_config.py

Comment thread codeframe/core/conductor.py
Comment thread codeframe/core/conductor.py
engine: str = "react"
stall_timeout_s: int = 300
stall_action: str = "blocker"
concurrency: ConcurrencyConfig = field(default_factory=ConcurrencyConfig)

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.

⚠️ Potential issue | 🟡 Minor

concurrency field is not persisted to database.

The concurrency field is added to BatchRun, but _save_batch (line 1957) and _row_to_batch (line 1997) don't handle it. This means:

  1. Concurrency config is lost when a batch is resumed
  2. get_batch() returns a BatchRun with default ConcurrencyConfig regardless of what was originally set

This may be acceptable if the feature is only used for the initial execution, but it's worth documenting or fixing for consistency with resume_batch().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/conductor.py` at line 532, The new concurrency:
ConcurrencyConfig field on BatchRun is never persisted or restored, so update
persistence to serialize and deserialize it: modify _save_batch to include the
batch.concurrency (e.g., as a JSON/text column or separate columns) when writing
the batch row, and modify _row_to_batch to parse that stored value back into a
ConcurrencyConfig instance and assign it to BatchRun.concurrency; ensure
get_batch() and resume_batch() will therefore return the actual saved
ConcurrencyConfig rather than the default. Use the existing BatchRun,
ConcurrencyConfig, _save_batch, _row_to_batch and resume_batch symbols to locate
the changes.

…l dispatch

- Use per-status limits when computing effective_workers for group execution
- Improve error message for non-integer values in parse_concurrency_by_status
@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Follow-up review of commit 18db151. The wiring fix is good - effective_workers() is now correctly called in _execute_parallel(). Remaining items: (1) SQLite persistence gap - concurrency field not serialized in _save_batch/_row_to_batch, batch resume will silently drop per-status limits; (2) global_running=0 hardcoded, so global max_parallel cap is not enforced; (3) non-positive values accepted by parse_concurrency_by_status without error; (4) BatchConfig to ConcurrencyConfig bridge still absent. The persistence gap is the most likely real user-visible bug. Happy to approve once addressed or documented as known limitation.

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

CORRECTION - Full review follows below. Please disregard the previous abbreviated comment.

@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Full Follow-up Review - Commit 18db151

Thanks for the quick follow-up commit. Acknowledging what changed and what still needs attention.

Critical issue resolved: The second commit (18db151) wired effective_workers() into _execute_parallel(). The per-status branch now correctly queries task statuses at dispatch time and calls batch.concurrency.effective_workers(). That was the core gap - good fix.

Remaining issues:

  1. SQLite persistence still missing (medium): _save_batch() and _row_to_batch() have not been updated. The concurrency field is not serialized to the DB. The INSERT has only 11 columns with no concurrency/by_status. When get_batch() or resume_batch() loads a batch from the DB, the reconstructed BatchRun.concurrency will be a default ConcurrencyConfig(by_status={}) - silently dropping any per-status limits that were originally set. This is the most concrete correctness issue remaining.

  2. global_running=0 hardcoded (medium): The global pool cap in effective_workers() only works if global_running reflects actual in-flight tasks. Passing 0 unconditionally means the global max_parallel ceiling is never enforced at the effective_workers() level. At minimum a TODO comment acknowledging this would help.

  3. Input validation: non-positive values still accepted (low): parse_concurrency_by_status with READY=0 or READY=-1 both succeed silently. The commit improved the error message for non-integers, but a guard raising ValueError for values less than 1 is still missing. effective_workers() clamps to max(1, ...) so it will not crash, but accepting 0 as config is misleading.

  4. BatchConfig to ConcurrencyConfig bridge still absent (low): EnvironmentConfig.batch (loaded from config.yaml) is still not consumed by start_batch(). The config round-trip test passes but the values are dead code in practice. Worth a TODO or follow-up issue so it does not get forgotten.

Summary: The wiring fix is the right move. The persistence gap (item 1) is the remaining issue most likely to cause a real user-visible bug (batch resume dropping limits). The other items are lower priority. Happy to approve once the persistence gap is addressed or documented as a known limitation.

@frankbria
frankbria merged commit e75afed into main Mar 13, 2026
10 checks passed
@frankbria
frankbria deleted the feature/issue-407-per-state-concurrency branch March 24, 2026 23:28
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.

[Phase 4] Per-State Concurrency Limits for Batch Execution

1 participant