Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions src/autoskillit/cli/_prompts_kitchen.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ def _build_open_kitchen_prompt(
def _build_fleet_dispatch_prompt(
mcp_prefix: str,
recipe_table: str | None = None,
max_issues_per_food_truck: int = 3,
max_total_issues: int = 12,
max_concurrent_dispatches: int = 3,
has_unguarded_filesystem_access: bool = False,
Expand Down Expand Up @@ -265,38 +264,6 @@ def _build_fleet_dispatch_prompt(

BEM already caps parallel group sizes via max_parallel (default 6 issues per group).

## BATCH DISPATCH MODE β€” OPTIONAL

When dispatching the `implementation` recipe for multiple issues and the user has set the
`batch_dispatch` ingredient to `"true"`, use batched dispatch instead of per-issue dispatch:

1. After BEM produces the dispatch_plan, for each parallel group (parallel: true):
a. Chunk the group's issues into sub-groups of up to {max_issues_per_food_truck} issues.
b. Dispatch each chunk as a SINGLE `implement-findings` food truck:
{mcp_prefix}dispatch_food_truck(
recipe="implement-findings",
task="Implement issues #X, #Y, #Z β€” parallel-safe per BEM group {{N}}",
ingredients={{{{
"issue_urls": "<comma-separated issue URLs for this chunk>",
"execution_map": "<captured execution_map from bem-wrapper>",
"base_branch": "<target branch>",
}}}},
dispatch_name="implement-g{{N}}-{{letter}}",
)
c. Name chunks sequentially: `implement-g{{N}}-a`, `-b`, `-c` …
d. Issue ALL chunks for a parallel group in a single response (parallel tool calls).
The fleet semaphore gates actual concurrency β€” if FLEET_PARALLEL_REFUSED is returned,
wait for a running dispatch to complete and retry.

2. Groups with only 1 issue or sequential groups (parallel: false): dispatch via the
`implementation` recipe as normal β€” one food truck per issue, not batched.

3. When `batch_dispatch` is `"false"` (the default): use the standard per-issue dispatch
from Step 4 above β€” one `implementation` food truck per issue. Do NOT use implement-findings.

4. The execution_map captured from bem-wrapper MUST be passed as an ingredient to each
implement-findings food truck so BEM is not re-run inside the food truck.

## DISPATCHER DISCIPLINE

You are a fleet dispatcher β€” NOT an executor. ALL recipe execution must be delegated \
Expand Down
1 change: 0 additions & 1 deletion src/autoskillit/cli/fleet/_fleet_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def _launch_fleet_session(
prompt = _build_fleet_dispatch_prompt(
mcp_prefix,
recipe_table=recipe_table,
max_issues_per_food_truck=cfg.fleet.max_issues_per_food_truck,
max_total_issues=cfg.fleet.max_total_issues,
max_concurrent_dispatches=cfg.fleet.max_concurrent_dispatches,
has_unguarded_filesystem_access=_backend_caps.has_unguarded_filesystem_access,
Expand Down
2 changes: 1 addition & 1 deletion src/autoskillit/recipes/diagrams/implementation.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- autoskillit-recipe-hash: sha256:e5972fabca3a0bb471f4f99688ceaa78127056b33100574f05f355712fe50ffb -->
<!-- autoskillit-recipe-hash: sha256:d3afca55385bea3028a28ec4354299c0fa8c03318976b921db7354eff380be57 -->
<!-- autoskillit-diagram-format: v7 -->
# implementation

Expand Down
4 changes: 0 additions & 4 deletions src/autoskillit/recipes/implementation.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@
"description": "Automatically merge the PR after required checks pass β€” via merge queue when available, direct merge otherwise",
"default": "true"
},
"batch_dispatch": {
"description": "Pack multiple parallel-safe issues per food truck. When \"true\", the fleet dispatcher batches parallel-safe issues (up to max_issues_per_food_truck) into implement-findings food trucks with comma-separated issue_urls.",
"default": "false"
},
"review_max_retries": {
"description": "Maximum number of review-resolve retry cycles before proceeding to CI",
"default": "3"
Expand Down
5 changes: 0 additions & 5 deletions src/autoskillit/recipes/implementation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ ingredients:
description: Automatically merge the PR after required checks pass β€” via merge
queue when available, direct merge otherwise
default: 'true'
batch_dispatch:
description: Pack multiple parallel-safe issues per food truck. When "true", the
fleet dispatcher batches parallel-safe issues (up to max_issues_per_food_truck)
into implement-findings food trucks with comma-separated issue_urls.
default: 'false'
review_max_retries:
description: Maximum number of review-resolve retry cycles before proceeding to
CI
Expand Down
35 changes: 8 additions & 27 deletions tests/cli/test_fleet_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,33 +809,7 @@ async def test_build_fleet_campaign_prompt_accepts_prior_dispatch_id_parameter(


class TestLaunchFleetSessionMaxIssuesPerFoodTruck:
"""Contract: _launch_fleet_session threads max_issues_per_food_truck to prompt builders."""

def test_adhoc_dispatch_forwards_max_issues_per_food_truck(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Ad-hoc threads cfg.fleet.max_issues_per_food_truck to _build_fleet_dispatch_prompt."""
captured: dict = {}

def _fake_build(*args: object, **kwargs: object) -> str:
captured.update(kwargs)
return "fake-prompt"

monkeypatch.setattr("autoskillit.cli._prompts._build_fleet_dispatch_prompt", _fake_build)
monkeypatch.setattr(
"autoskillit.cli.session._session_launch._run_interactive_session",
lambda *a, **kw: None,
)

config_dir = tmp_path / ".autoskillit"
config_dir.mkdir(exist_ok=True)
(config_dir / "config.yaml").write_text("fleet:\n max_issues_per_food_truck: 7\n")
monkeypatch.chdir(tmp_path)

from autoskillit.cli.fleet._fleet_session import _launch_fleet_session

_launch_fleet_session(None, None, None, None, fleet_mode="dispatch")
assert captured.get("max_issues_per_food_truck") == 7
"""Contract: max_issues_per_food_truck flows to campaign builder only, not dispatch builder."""

def test_campaign_dispatch_forwards_max_issues_per_food_truck(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
Expand Down Expand Up @@ -879,3 +853,10 @@ def _fake_build(
fleet_mode="campaign",
)
assert captured.get("max_issues_per_food_truck") == 7

def test_dispatch_prompt_does_not_accept_max_issues_per_food_truck(self) -> None:
"""_build_fleet_dispatch_prompt must not expose max_issues_per_food_truck parameter."""
from autoskillit.cli._prompts_kitchen import _build_fleet_dispatch_prompt

sig = inspect.signature(_build_fleet_dispatch_prompt)
assert "max_issues_per_food_truck" not in sig.parameters
45 changes: 0 additions & 45 deletions tests/contracts/test_fleet_dispatch_bem_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,51 +98,6 @@ def test_fleet_prompt_bem_gate_has_never_block(fleet_prompt: str) -> None:
)


def test_fleet_prompt_contains_batch_dispatch_section(fleet_prompt: str) -> None:
"""Fleet dispatcher prompt must contain batch dispatch mode section."""
assert "BATCH DISPATCH" in fleet_prompt


def test_fleet_prompt_batch_section_references_implement_findings(fleet_prompt: str) -> None:
"""Batch dispatch section must reference implement-findings recipe."""
batch_section = fleet_prompt.split("BATCH DISPATCH")[1].split("## DISPATCHER")[0]
assert "implement-findings" in batch_section


def test_fleet_prompt_batch_section_references_execution_map(fleet_prompt: str) -> None:
"""Batch dispatch section must instruct passing execution_map to implement-findings."""
batch_section = fleet_prompt.split("BATCH DISPATCH")[1].split("## DISPATCHER")[0]
assert "execution_map" in batch_section


def test_fleet_prompt_batch_section_contains_max_issues_cap(fleet_prompt: str) -> None:
"""Batch dispatch section must contain the max_issues_per_food_truck cap value."""
batch_section = fleet_prompt.split("BATCH DISPATCH")[1].split("## DISPATCHER")[0]
assert "3" in batch_section


def test_fleet_prompt_batch_section_preserves_single_issue_fallback(fleet_prompt: str) -> None:
"""Batch section must instruct fallback to implementation recipe for single-issue groups."""
batch_section = fleet_prompt.split("BATCH DISPATCH")[1].split("## DISPATCHER")[0]
lower = batch_section.lower()
assert "single" in lower or "1 issue" in lower or "only 1" in lower


def test_implementation_recipe_has_batch_dispatch_ingredient() -> None:
"""implementation.yaml must expose a visible batch_dispatch ingredient."""
from autoskillit.core.io import load_yaml
from autoskillit.core.paths import pkg_root

recipe_yaml = load_yaml(pkg_root() / "recipes" / "implementation.yaml")
ingredients = recipe_yaml["ingredients"]
assert "batch_dispatch" in ingredients, (
"implementation.yaml must have batch_dispatch ingredient"
)
bd = ingredients["batch_dispatch"]
assert bd.get("default") == "false", "batch_dispatch must default to 'false'"
assert bd.get("hidden") is not True, "batch_dispatch must be visible (not hidden)"


def test_fleet_prompt_contains_gated_group_handling(fleet_prompt: str) -> None:
assert "gated" in fleet_prompt.lower(), (
"Fleet dispatcher prompt must instruct handling of gated groups "
Expand Down
Loading