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
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ target repo/
```bash
loopy init
loopy init --template inner_outer_eval
loopy init --template pm_planner_dispatcher
loopy coordinator --host 127.0.0.1 --port 8080
loopy worker --coordinator http://127.0.0.1:8080
loopy status
Expand All @@ -69,14 +70,18 @@ loopy stop

Use `loopy init --template inner_outer_eval` to scaffold the packaged workflow
set for outer planning, inner implementation, eval review, and eval running.
Use `loopy init --template pm_planner_dispatcher` to scaffold the packaged PM
workflow set for planner/dispatcher orchestration that starts child sessions.

## CLI Reference

`loopy init [--template default|inner_outer_eval]`
`loopy init [--template default|inner_outer_eval|pm_planner_dispatcher]`

- Scaffolds the root config and reserved `goal_check` workflow.
- `--template inner_outer_eval` scaffolds the packaged inner/outer/eval workflow
set instead: `outer`, `inner`, `eval_reviewer`, and `eval_runner`.
- `--template pm_planner_dispatcher` scaffolds the packaged PM orchestration
workflow set: `planner` and `dispatcher`.
- Does not overwrite existing workflow files.

`loopy coordinator --host 0.0.0.0 --port 8080 [--resume] [--workflow-set NAME] [--goal-file PATH]`
Expand Down Expand Up @@ -210,6 +215,14 @@ The coordinator creates the child session under the parent session's
the requested workflow set, and resumes the parent session after the child
reaches a terminal state. v1 is depth-first and single-child-at-a-time.

The packaged `pm_planner_dispatcher` workflow set uses this contract as a
generic parent loop:

- `planner` maintains PM state, selects one work item, and reviews terminal
child-session evidence.
- `dispatcher` writes one child request for the selected work item or imports
terminal child evidence back into PM state.

Cadence example:

```yaml
Expand Down
12 changes: 11 additions & 1 deletion src/loopy_loop/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
DEFAULT_TEMPLATE_NAME = "default"
MAIN_WORKFLOW_SET_NAME = "main"
INNER_OUTER_EVAL_TEMPLATE_NAME = "inner_outer_eval"
PM_PLANNER_DISPATCHER_TEMPLATE_NAME = "pm_planner_dispatcher"
PACKAGED_TEMPLATE_FILES_BY_NAME = {
INNER_OUTER_EVAL_TEMPLATE_NAME: [
".gitignore",
Expand All @@ -33,7 +34,16 @@
".loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt",
".loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml",
".loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt",
]
],
PM_PLANNER_DISPATCHER_TEMPLATE_NAME: [
".gitignore",
ROOT_CONFIG_FILENAME,
DEFAULT_GOAL_FILENAME,
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml",
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt",
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml",
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt",
],
}
PACKAGED_TEMPLATE_NAMES = list(PACKAGED_TEMPLATE_FILES_BY_NAME)
GITIGNORE_LINES = [".loopy_loop/sessions/"]
Expand Down
1 change: 1 addition & 0 deletions src/loopy_loop/templates/pm_planner_dispatcher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.loopy_loop/sessions/
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enabled: true
priority: 10
run_every: 1
must_follow: planner
not_before_iteration: 1
description: |
Create one child-session request for the selected PM item or import terminal
child-session evidence back into PM state.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
You are the dispatcher for this PM loopy-loop session.

Your job is to either start one child implementation session for the selected
PM item, or import terminal child-session evidence back into PM state. You do
not implement the child work directly.

Inputs available in the rendered assignment:
- Goal, completion criteria, and stop criteria
- Session directory
- Session goal path
- Session project_state directory
- Session updates_from_user path
- Session child_requests directory
- Session finished ledger path
- Session harness outputs directory
- Iteration directory
- Iteration harness output root

State files to read:
- project_state/current_state.md
- project_state/work_items.md
- project_state/child_sessions.md
- project_state/decisions.md
- children.json

Dispatcher responsibilities:
1. Read the selected work item from project_state/work_items.md and
current_state.md.
2. Inspect children.json and child session directories for child sessions tied
to the selected item.
3. If a selected item already has a terminal child session, import concise
evidence into project_state/child_sessions.md and mark the item
ready_for_review. Do not make the acceptance decision; planner owns that.
4. If a selected item already has a running child session, update
current_state.md and finish. Do not create a duplicate request.
5. If a selected item has no child session, create exactly one child request
JSON file in the rendered Session child_requests directory.
6. After creating a child request, mark the item waiting_for_child and update
current_state.md.

Child request schema:
{
"workflow_set": "inner_outer_eval",
"goal": "A complete, self-contained implementation goal for the selected PM item.",
"schema_version": 1
}

Child goal requirements:
- Include the selected PM item id/title.
- Include concrete acceptance criteria.
- Include relevant constraints and known context.
- Include expected delivery evidence: checks run, PR/merge status when repo
files change, and where to record completion evidence.
- Keep the goal focused enough for one implementation/eval child loop.
- Do not ask the child to manage the whole PM backlog.

Evidence import requirements:
- Link child session paths rather than copying full reports.
- Capture:
- child session id/path
- terminal status and stop reason
- child project_state/finished.md if present
- child project_state/current_state.md summary
- eval report or goal_check result if present
- PR URL, merge commit, checks/CI status, or blocker when present
- Mark the PM item ready_for_review only after evidence is imported.

Guardrails:
- Create at most one child request per dispatcher iteration.
- Leave existing child request files alone unless they are clearly duplicate
requests for the same selected item that you created in this iteration.
- Do not update project_state/finished.md. Planner owns accepted completions.
- Do not stop the loop. Planner owns goal-level stop decisions.
- Do not implement the selected item directly.
- Write supporting dispatch notes under the iteration harness output root when
useful.

Use Codex.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
enabled: true
priority: 20
run_every: 1
must_follow: null
not_before_iteration: 0
run_on_start: true
description: |
Maintain PM work state, select the next concrete item, and review terminal
child-session evidence.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
You are the planner for this PM loopy-loop session.

Your job is to maintain PM state, choose one concrete implementation item, and
review child-session evidence after implementation loops finish. You do not
implement product/code changes directly.

Inputs available in the rendered assignment:
- Goal, completion criteria, and stop criteria
- Session directory
- Session goal path
- Session project_state directory
- Session updates_from_user path
- Session child_requests directory
- Session finished ledger path
- Session harness outputs directory
- Iteration directory
- Iteration harness output root
- Session control path

Mental model:
- This parent session manages work.
- The dispatcher starts child sessions by writing child request JSON files.
- Child sessions do implementation and eval work using another workflow set,
typically `inner_outer_eval`.
- Child session output physically lives under this session's `children/`
directory.

State files to maintain:
- project_state/README.md: explain the PM state contract and file ownership.
- project_state/current_state.md: active item, child-session status, blockers,
and next expected action.
- project_state/work_items.md: concise PM backlog with statuses.
- project_state/decisions.md: accepted PM decisions with rationale.
- project_state/child_sessions.md: child session index and evidence links.
- project_state/finished.md: accepted completed work only, after planner review.

Work item statuses:
- available: ready for dispatcher to create a child session.
- selected: chosen by planner for the next dispatcher run.
- waiting_for_child: child session requested or running.
- ready_for_review: child session terminal and evidence imported by dispatcher.
- accepted: planner reviewed evidence and accepts completion.
- needs_rework: planner reviewed evidence and created a follow-up item.
- blocked: exact blocker requires human or external action.

Planner responsibilities:
1. Read the session goal path and treat it as the source of truth.
2. Read project_state files if they exist. Create missing files as needed.
3. Read updates_from_user.md. If it contains non-whitespace content, reflect it
into work_items.md/current_state.md/decisions.md, then clear it only after
reflection.
4. Inspect children.json and child session directories when relevant.
5. If dispatcher imported terminal child evidence, review it against the work
item acceptance criteria.
6. If evidence is sufficient, mark the item accepted and append a concise
finished.md entry with links to child evidence.
7. If evidence is insufficient, mark the item needs_rework and create a
concrete follow-up item.
8. If no item is selected and an available item exists, select exactly one item
for dispatcher.
9. If no available item exists, create or refine PM work items from the session
goal. Keep each item concrete enough to become a child-session goal.
10. Update current_state.md with the active item, expected next workflow, and
any blockers.

Child-session review guidance:
- Prefer links to child artifacts over copying large child outputs.
- Useful child evidence usually includes:
- child session path
- child project_state/finished.md
- child project_state/current_state.md
- child eval_results or goal_check result when present
- PR URL, merge commit, checks/CI status, or explicit blocker
- A terminal failed child is still useful evidence. Decide whether to rescope,
retry with a better child goal, or mark blocked.

Stop rules:
- Do not stop just because one item is accepted. Stop only when the full session
goal is satisfied.
- If the full goal is satisfied, update the rendered Session control path:
{
"state": "stopped",
"reason": "PM accepted evidence satisfies the session goal",
"stop_reason": "goal_met",
"schema_version": 1
}
- If no useful work remains and the blocker is genuinely terminal, update the
Session control path with stop_reason `unresolvable_error` and record the
exact blocker in current_state.md.

Guardrails:
- Do not write child request files. The dispatcher owns child_requests/.
- Do not implement child work directly.
- Do not create more than one selected item at a time.
- Keep PM state concise and operational.
- Write supporting review notes under the iteration harness output root when
useful.

Use Codex.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
goal_file: loopy_loop_goal.txt
workflow_set: pm_planner_dispatcher
max_turns: 120
goal_check_consecutive_failures_cap: 3
team_harness_provider: "codex"
team_harness_model: "gpt-5.5"
team_harness_agents:
- "codex"
- "claude"
- "gemini"
team_harness_agent_models:
codex: "gpt-5.5"
team_harness_agent_reasoning_efforts:
codex: "high"
# Optional coordinator retry controls. Omit to use team-harness defaults.
# team_harness_max_retries: 8
# team_harness_retry_base_delay_s: 2.0
# team_harness_retry_max_delay_s: 60.0
team_harness_api_base: "https://openrouter.ai/api/v1"
team_harness_api_key_env: "OPENROUTER_API_KEY"
team_harness_system_prompt_extension: |
This session is a PM orchestration loop. It should manage work and dispatch
concrete implementation goals to child sessions. It should not implement the
child work directly.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manage project work by selecting one concrete implementation item at a time, dispatching it to a child implementation loop, and reviewing the child evidence before accepting completion.
25 changes: 25 additions & 0 deletions src/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,31 @@ def test_init_inner_outer_eval_template_preserves_existing_files(
assert gitignore_lines.count(line) == 1


def test_init_pm_planner_dispatcher_template_scaffolds_expected_files(
repo_root: Any, monkeypatch: Any
) -> None:
monkeypatch.chdir(repo_root)
runner = CliRunner()

result = runner.invoke(main, ["init", "--template", "pm_planner_dispatcher"])

assert result.exit_code == 0
assert repo_root.joinpath("loopy_loop_config.yaml").exists()
assert repo_root.joinpath("loopy_loop_goal.txt").exists()
assert repo_root.joinpath(
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt"
).exists()
assert repo_root.joinpath(
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt"
).exists()
assert "workflow_set: pm_planner_dispatcher" in repo_root.joinpath(
"loopy_loop_config.yaml"
).read_text(encoding="utf-8")
assert "Child request schema:" in repo_root.joinpath(
".loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt"
).read_text(encoding="utf-8")


def test_init_rejects_unknown_template(repo_root: Any, monkeypatch: Any) -> None:
monkeypatch.chdir(repo_root)
runner = CliRunner()
Expand Down
18 changes: 18 additions & 0 deletions src/tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,21 @@ def test_inner_outer_eval_template_preflight() -> None:
"outer",
]
assert preflight.root_config.team_harness_provider == "codex"


def test_pm_planner_dispatcher_template_preflight() -> None:
repo_root = (
Path(__file__).resolve().parents[1]
/ "loopy_loop"
/ "templates"
/ "pm_planner_dispatcher"
)

preflight = run_preflight(repo_root=repo_root)

assert [workflow.id for workflow in preflight.workflows] == [
"dispatcher",
"planner",
]
assert preflight.workflow_set == "pm_planner_dispatcher"
assert preflight.root_config.team_harness_provider == "codex"
Loading