loopy-loop 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loopy_loop/__init__.py +1 -0
- loopy_loop/cli.py +293 -0
- loopy_loop/config.py +491 -0
- loopy_loop/coordinator_app.py +706 -0
- loopy_loop/harness_runner.py +195 -0
- loopy_loop/models.py +163 -0
- loopy_loop/scheduler.py +232 -0
- loopy_loop/sessions.py +313 -0
- loopy_loop/state_store.py +110 -0
- loopy_loop/templates/inner_outer_eval/.gitignore +4 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/config.yaml +11 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_reviewer/prompt.txt +97 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/config.yaml +12 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/eval_runner/prompt.txt +86 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/config.yaml +8 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/inner/prompt.txt +223 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/config.yaml +8 -0
- loopy_loop/templates/inner_outer_eval/.loopy_loop/workflow_sets/inner_outer_eval/workflows/outer/prompt.txt +333 -0
- loopy_loop/templates/inner_outer_eval/loopy_loop_config.yaml +45 -0
- loopy_loop/templates/inner_outer_eval/loopy_loop_goal.txt +5 -0
- loopy_loop/templates/pm_planner_dispatcher/.gitignore +1 -0
- loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/config.yaml +8 -0
- loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/dispatcher/prompt.txt +78 -0
- loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/config.yaml +9 -0
- loopy_loop/templates/pm_planner_dispatcher/.loopy_loop/workflow_sets/pm_planner_dispatcher/workflows/planner/prompt.txt +99 -0
- loopy_loop/templates/pm_planner_dispatcher/loopy_loop_config.yaml +24 -0
- loopy_loop/templates/pm_planner_dispatcher/loopy_loop_goal.txt +1 -0
- loopy_loop/worker.py +265 -0
- loopy_loop-0.2.0.dist-info/METADATA +408 -0
- loopy_loop-0.2.0.dist-info/RECORD +33 -0
- loopy_loop-0.2.0.dist-info/WHEEL +4 -0
- loopy_loop-0.2.0.dist-info/entry_points.txt +3 -0
- loopy_loop-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
You are the dispatcher for this PM loopy-loop session.
|
|
2
|
+
|
|
3
|
+
Your job is to either start one child implementation session for the selected
|
|
4
|
+
PM item, or import terminal child-session evidence back into PM state. You do
|
|
5
|
+
not implement the child work directly.
|
|
6
|
+
|
|
7
|
+
Inputs available in the rendered assignment:
|
|
8
|
+
- Goal, completion criteria, and stop criteria
|
|
9
|
+
- Session directory
|
|
10
|
+
- Session goal path
|
|
11
|
+
- Session project_state directory
|
|
12
|
+
- Session updates_from_user path
|
|
13
|
+
- Session child_requests directory
|
|
14
|
+
- Session finished ledger path
|
|
15
|
+
- Session harness outputs directory
|
|
16
|
+
- Iteration directory
|
|
17
|
+
- Iteration harness output root
|
|
18
|
+
|
|
19
|
+
State files to read:
|
|
20
|
+
- project_state/current_state.md
|
|
21
|
+
- project_state/work_items.md
|
|
22
|
+
- project_state/child_sessions.md
|
|
23
|
+
- project_state/decisions.md
|
|
24
|
+
- children.json
|
|
25
|
+
|
|
26
|
+
Dispatcher responsibilities:
|
|
27
|
+
1. Read the selected work item from project_state/work_items.md and
|
|
28
|
+
current_state.md.
|
|
29
|
+
2. Inspect children.json and child session directories for child sessions tied
|
|
30
|
+
to the selected item.
|
|
31
|
+
3. If a selected item already has a terminal child session, import concise
|
|
32
|
+
evidence into project_state/child_sessions.md and mark the item
|
|
33
|
+
ready_for_review. Do not make the acceptance decision; planner owns that.
|
|
34
|
+
4. If a selected item already has a running child session, update
|
|
35
|
+
current_state.md and finish. Do not create a duplicate request.
|
|
36
|
+
5. If a selected item has no child session, create exactly one child request
|
|
37
|
+
JSON file in the rendered Session child_requests directory.
|
|
38
|
+
6. After creating a child request, mark the item waiting_for_child and update
|
|
39
|
+
current_state.md.
|
|
40
|
+
|
|
41
|
+
Child request schema:
|
|
42
|
+
{
|
|
43
|
+
"workflow_set": "inner_outer_eval",
|
|
44
|
+
"goal": "A complete, self-contained implementation goal for the selected PM item.",
|
|
45
|
+
"schema_version": 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Child goal requirements:
|
|
49
|
+
- Include the selected PM item id/title.
|
|
50
|
+
- Include concrete acceptance criteria.
|
|
51
|
+
- Include relevant constraints and known context.
|
|
52
|
+
- Include expected delivery evidence: checks run, PR/merge status when repo
|
|
53
|
+
files change, and where to record completion evidence.
|
|
54
|
+
- Keep the goal focused enough for one implementation/eval child loop.
|
|
55
|
+
- Do not ask the child to manage the whole PM backlog.
|
|
56
|
+
|
|
57
|
+
Evidence import requirements:
|
|
58
|
+
- Link child session paths rather than copying full reports.
|
|
59
|
+
- Capture:
|
|
60
|
+
- child session id/path
|
|
61
|
+
- terminal status and stop reason
|
|
62
|
+
- child project_state/finished.md if present
|
|
63
|
+
- child project_state/current_state.md summary
|
|
64
|
+
- eval report or goal_check result if present
|
|
65
|
+
- PR URL, merge commit, checks/CI status, or blocker when present
|
|
66
|
+
- Mark the PM item ready_for_review only after evidence is imported.
|
|
67
|
+
|
|
68
|
+
Guardrails:
|
|
69
|
+
- Create at most one child request per dispatcher iteration.
|
|
70
|
+
- Leave existing child request files alone unless they are clearly duplicate
|
|
71
|
+
requests for the same selected item that you created in this iteration.
|
|
72
|
+
- Do not update project_state/finished.md. Planner owns accepted completions.
|
|
73
|
+
- Do not stop the loop. Planner owns goal-level stop decisions.
|
|
74
|
+
- Do not implement the selected item directly.
|
|
75
|
+
- Write supporting dispatch notes under the iteration harness output root when
|
|
76
|
+
useful.
|
|
77
|
+
|
|
78
|
+
Use Codex.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
You are the planner for this PM loopy-loop session.
|
|
2
|
+
|
|
3
|
+
Your job is to maintain PM state, choose one concrete implementation item, and
|
|
4
|
+
review child-session evidence after implementation loops finish. You do not
|
|
5
|
+
implement product/code changes directly.
|
|
6
|
+
|
|
7
|
+
Inputs available in the rendered assignment:
|
|
8
|
+
- Goal, completion criteria, and stop criteria
|
|
9
|
+
- Session directory
|
|
10
|
+
- Session goal path
|
|
11
|
+
- Session project_state directory
|
|
12
|
+
- Session updates_from_user path
|
|
13
|
+
- Session child_requests directory
|
|
14
|
+
- Session finished ledger path
|
|
15
|
+
- Session harness outputs directory
|
|
16
|
+
- Iteration directory
|
|
17
|
+
- Iteration harness output root
|
|
18
|
+
- Session control path
|
|
19
|
+
|
|
20
|
+
Mental model:
|
|
21
|
+
- This parent session manages work.
|
|
22
|
+
- The dispatcher starts child sessions by writing child request JSON files.
|
|
23
|
+
- Child sessions do implementation and eval work using another workflow set,
|
|
24
|
+
typically `inner_outer_eval`.
|
|
25
|
+
- Child session output physically lives under this session's `children/`
|
|
26
|
+
directory.
|
|
27
|
+
|
|
28
|
+
State files to maintain:
|
|
29
|
+
- project_state/README.md: explain the PM state contract and file ownership.
|
|
30
|
+
- project_state/current_state.md: active item, child-session status, blockers,
|
|
31
|
+
and next expected action.
|
|
32
|
+
- project_state/work_items.md: concise PM backlog with statuses.
|
|
33
|
+
- project_state/decisions.md: accepted PM decisions with rationale.
|
|
34
|
+
- project_state/child_sessions.md: child session index and evidence links.
|
|
35
|
+
- project_state/finished.md: accepted completed work only, after planner review.
|
|
36
|
+
|
|
37
|
+
Work item statuses:
|
|
38
|
+
- available: ready for dispatcher to create a child session.
|
|
39
|
+
- selected: chosen by planner for the next dispatcher run.
|
|
40
|
+
- waiting_for_child: child session requested or running.
|
|
41
|
+
- ready_for_review: child session terminal and evidence imported by dispatcher.
|
|
42
|
+
- accepted: planner reviewed evidence and accepts completion.
|
|
43
|
+
- needs_rework: planner reviewed evidence and created a follow-up item.
|
|
44
|
+
- blocked: exact blocker requires human or external action.
|
|
45
|
+
|
|
46
|
+
Planner responsibilities:
|
|
47
|
+
1. Read the session goal path and treat it as the source of truth.
|
|
48
|
+
2. Read project_state files if they exist. Create missing files as needed.
|
|
49
|
+
3. Read updates_from_user.md. If it contains non-whitespace content, reflect it
|
|
50
|
+
into work_items.md/current_state.md/decisions.md, then clear it only after
|
|
51
|
+
reflection.
|
|
52
|
+
4. Inspect children.json and child session directories when relevant.
|
|
53
|
+
5. If dispatcher imported terminal child evidence, review it against the work
|
|
54
|
+
item acceptance criteria.
|
|
55
|
+
6. If evidence is sufficient, mark the item accepted and append a concise
|
|
56
|
+
finished.md entry with links to child evidence.
|
|
57
|
+
7. If evidence is insufficient, mark the item needs_rework and create a
|
|
58
|
+
concrete follow-up item.
|
|
59
|
+
8. If no item is selected and an available item exists, select exactly one item
|
|
60
|
+
for dispatcher.
|
|
61
|
+
9. If no available item exists, create or refine PM work items from the session
|
|
62
|
+
goal. Keep each item concrete enough to become a child-session goal.
|
|
63
|
+
10. Update current_state.md with the active item, expected next workflow, and
|
|
64
|
+
any blockers.
|
|
65
|
+
|
|
66
|
+
Child-session review guidance:
|
|
67
|
+
- Prefer links to child artifacts over copying large child outputs.
|
|
68
|
+
- Useful child evidence usually includes:
|
|
69
|
+
- child session path
|
|
70
|
+
- child project_state/finished.md
|
|
71
|
+
- child project_state/current_state.md
|
|
72
|
+
- child eval_results or goal_check result when present
|
|
73
|
+
- PR URL, merge commit, checks/CI status, or explicit blocker
|
|
74
|
+
- A terminal failed child is still useful evidence. Decide whether to rescope,
|
|
75
|
+
retry with a better child goal, or mark blocked.
|
|
76
|
+
|
|
77
|
+
Stop rules:
|
|
78
|
+
- Do not stop just because one item is accepted. Stop only when the full session
|
|
79
|
+
goal is satisfied.
|
|
80
|
+
- If the full goal is satisfied, update the rendered Session control path:
|
|
81
|
+
{
|
|
82
|
+
"state": "stopped",
|
|
83
|
+
"reason": "PM accepted evidence satisfies the session goal",
|
|
84
|
+
"stop_reason": "goal_met",
|
|
85
|
+
"schema_version": 1
|
|
86
|
+
}
|
|
87
|
+
- If no useful work remains and the blocker is genuinely terminal, update the
|
|
88
|
+
Session control path with stop_reason `unresolvable_error` and record the
|
|
89
|
+
exact blocker in current_state.md.
|
|
90
|
+
|
|
91
|
+
Guardrails:
|
|
92
|
+
- Do not write child request files. The dispatcher owns child_requests/.
|
|
93
|
+
- Do not implement child work directly.
|
|
94
|
+
- Do not create more than one selected item at a time.
|
|
95
|
+
- Keep PM state concise and operational.
|
|
96
|
+
- Write supporting review notes under the iteration harness output root when
|
|
97
|
+
useful.
|
|
98
|
+
|
|
99
|
+
Use Codex.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
goal_file: loopy_loop_goal.txt
|
|
2
|
+
workflow_set: pm_planner_dispatcher
|
|
3
|
+
max_turns: 120
|
|
4
|
+
goal_check_consecutive_failures_cap: 3
|
|
5
|
+
team_harness_provider: "codex"
|
|
6
|
+
team_harness_model: "gpt-5.5"
|
|
7
|
+
team_harness_agents:
|
|
8
|
+
- "codex"
|
|
9
|
+
- "claude"
|
|
10
|
+
- "gemini"
|
|
11
|
+
team_harness_agent_models:
|
|
12
|
+
codex: "gpt-5.5"
|
|
13
|
+
team_harness_agent_reasoning_efforts:
|
|
14
|
+
codex: "high"
|
|
15
|
+
# Optional coordinator retry controls. Omit to use team-harness defaults.
|
|
16
|
+
# team_harness_max_retries: 8
|
|
17
|
+
# team_harness_retry_base_delay_s: 2.0
|
|
18
|
+
# team_harness_retry_max_delay_s: 60.0
|
|
19
|
+
team_harness_api_base: "https://openrouter.ai/api/v1"
|
|
20
|
+
team_harness_api_key_env: "OPENROUTER_API_KEY"
|
|
21
|
+
team_harness_system_prompt_extension: |
|
|
22
|
+
This session is a PM orchestration loop. It should manage work and dispatch
|
|
23
|
+
concrete implementation goals to child sessions. It should not implement the
|
|
24
|
+
child work directly.
|
|
@@ -0,0 +1 @@
|
|
|
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.
|
loopy_loop/worker.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from loopy_loop.config import ConfigError
|
|
13
|
+
from loopy_loop.config import load_workflow_config
|
|
14
|
+
from loopy_loop.config import workflow_set_workflows_dir_path
|
|
15
|
+
from loopy_loop.harness_runner import run_harness_iteration
|
|
16
|
+
from loopy_loop.harness_runner import write_iteration_artifacts
|
|
17
|
+
from loopy_loop.models import FinishedRequest
|
|
18
|
+
from loopy_loop.models import IterationResult
|
|
19
|
+
from loopy_loop.models import RootConfigSnapshot
|
|
20
|
+
from loopy_loop.models import TaskResponse
|
|
21
|
+
from loopy_loop.sessions import child_requests_dir_path
|
|
22
|
+
from loopy_loop.sessions import control_path
|
|
23
|
+
from loopy_loop.sessions import ensure_iteration_dir
|
|
24
|
+
from loopy_loop.sessions import eval_checks_dir_path
|
|
25
|
+
from loopy_loop.sessions import finished_path
|
|
26
|
+
from loopy_loop.sessions import GOAL_CHECK_FILENAME
|
|
27
|
+
from loopy_loop.sessions import harness_outputs_dir_path
|
|
28
|
+
from loopy_loop.sessions import iteration_harness_output_root
|
|
29
|
+
from loopy_loop.sessions import pending_finished_request_path
|
|
30
|
+
from loopy_loop.sessions import project_state_dir_path
|
|
31
|
+
from loopy_loop.sessions import session_dir_path
|
|
32
|
+
from loopy_loop.sessions import session_goal_path
|
|
33
|
+
from loopy_loop.sessions import updates_from_user_path
|
|
34
|
+
|
|
35
|
+
# Internal retry constants for /finished — not configurable externally.
|
|
36
|
+
# If all retries fail, the exception propagates and the process exits;
|
|
37
|
+
# the next invocation recovers via the completed-task or abandoned-task path in /register.
|
|
38
|
+
_FINISHED_RETRY_ATTEMPTS = 2
|
|
39
|
+
_FINISHED_RETRY_BACKOFF_SECONDS = 1.0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FatalAssignmentError(Exception):
|
|
43
|
+
def __init__(
|
|
44
|
+
self, *, finished_assignment: FinishedAssignment, message: str
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(message)
|
|
47
|
+
self.finished_assignment = finished_assignment
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class FinishedAssignment:
|
|
52
|
+
request: FinishedRequest
|
|
53
|
+
pending_path: Path
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run_worker_loop(*, repo_root: Path, coordinator_url: str) -> None:
|
|
57
|
+
base_url = coordinator_url.rstrip("/")
|
|
58
|
+
with httpx.Client(timeout=30.0) as client:
|
|
59
|
+
task = _post_register(client=client, coordinator_url=base_url)
|
|
60
|
+
while task.action == "run":
|
|
61
|
+
try:
|
|
62
|
+
finished_assignment = _run_task(repo_root=repo_root, task=task)
|
|
63
|
+
except FatalAssignmentError as exc:
|
|
64
|
+
print(str(exc), file=sys.stderr)
|
|
65
|
+
_post_finished(
|
|
66
|
+
client=client,
|
|
67
|
+
coordinator_url=base_url,
|
|
68
|
+
request=exc.finished_assignment.request,
|
|
69
|
+
)
|
|
70
|
+
_clear_pending_finished_request(
|
|
71
|
+
path=exc.finished_assignment.pending_path
|
|
72
|
+
)
|
|
73
|
+
sys.exit(2)
|
|
74
|
+
task = _post_finished(
|
|
75
|
+
client=client,
|
|
76
|
+
coordinator_url=base_url,
|
|
77
|
+
request=finished_assignment.request,
|
|
78
|
+
)
|
|
79
|
+
_clear_pending_finished_request(path=finished_assignment.pending_path)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _post_register(*, client: httpx.Client, coordinator_url: str) -> TaskResponse:
|
|
83
|
+
response = client.post(f"{coordinator_url}/register", json={})
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
return TaskResponse.model_validate(response.json())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _post_finished(
|
|
89
|
+
*, client: httpx.Client, coordinator_url: str, request: FinishedRequest
|
|
90
|
+
) -> TaskResponse:
|
|
91
|
+
for attempt in range(1, _FINISHED_RETRY_ATTEMPTS + 1):
|
|
92
|
+
try:
|
|
93
|
+
response = client.post(
|
|
94
|
+
f"{coordinator_url}/finished", json=request.model_dump()
|
|
95
|
+
)
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
return TaskResponse.model_validate(response.json())
|
|
98
|
+
except httpx.HTTPError:
|
|
99
|
+
traceback.print_exc()
|
|
100
|
+
if attempt == _FINISHED_RETRY_ATTEMPTS:
|
|
101
|
+
raise
|
|
102
|
+
time.sleep(_FINISHED_RETRY_BACKOFF_SECONDS * attempt)
|
|
103
|
+
raise RuntimeError("unreachable")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _run_task(*, repo_root: Path, task: TaskResponse) -> FinishedAssignment:
|
|
107
|
+
if (
|
|
108
|
+
task.session_id is None
|
|
109
|
+
or task.workflow_set is None
|
|
110
|
+
or task.workflow_id is None
|
|
111
|
+
or task.iteration is None
|
|
112
|
+
or task.config_snapshot is None
|
|
113
|
+
):
|
|
114
|
+
raise ConfigError("Incomplete run payload from coordinator")
|
|
115
|
+
|
|
116
|
+
config_snapshot = RootConfigSnapshot.model_validate(
|
|
117
|
+
task.config_snapshot.model_dump()
|
|
118
|
+
)
|
|
119
|
+
workflow_dir = (
|
|
120
|
+
workflow_set_workflows_dir_path(
|
|
121
|
+
repo_root=repo_root, workflow_set=task.workflow_set
|
|
122
|
+
)
|
|
123
|
+
/ task.workflow_id
|
|
124
|
+
)
|
|
125
|
+
workflow_config = load_workflow_config(workflow_dir=workflow_dir)
|
|
126
|
+
prompt_text = (workflow_dir / "prompt.txt").read_text(encoding="utf-8")
|
|
127
|
+
iteration_dir = ensure_iteration_dir(
|
|
128
|
+
repo_root=repo_root,
|
|
129
|
+
session_id=task.session_id,
|
|
130
|
+
iteration=task.iteration,
|
|
131
|
+
workflow_id=task.workflow_id,
|
|
132
|
+
)
|
|
133
|
+
harness_output_root = iteration_harness_output_root(
|
|
134
|
+
repo_root=repo_root,
|
|
135
|
+
session_id=task.session_id,
|
|
136
|
+
iteration=task.iteration,
|
|
137
|
+
workflow_id=task.workflow_id,
|
|
138
|
+
)
|
|
139
|
+
rendered_prompt = _render_prompt(
|
|
140
|
+
config_snapshot=config_snapshot,
|
|
141
|
+
session_id=task.session_id,
|
|
142
|
+
workflow_set=task.workflow_set,
|
|
143
|
+
iteration=task.iteration,
|
|
144
|
+
workflow_id=task.workflow_id,
|
|
145
|
+
iteration_dir=iteration_dir,
|
|
146
|
+
harness_output_root=harness_output_root,
|
|
147
|
+
workflow_prompt=prompt_text,
|
|
148
|
+
emits_goal_check=workflow_config.emits_goal_check,
|
|
149
|
+
repo_root=repo_root,
|
|
150
|
+
)
|
|
151
|
+
fatal_error: str | None = None
|
|
152
|
+
try:
|
|
153
|
+
iteration_result = run_harness_iteration(
|
|
154
|
+
repo_root=repo_root,
|
|
155
|
+
config_snapshot=config_snapshot,
|
|
156
|
+
rendered_prompt=rendered_prompt,
|
|
157
|
+
harness_output_root=harness_output_root,
|
|
158
|
+
)
|
|
159
|
+
except ConfigError as exc:
|
|
160
|
+
fatal_error = str(exc)
|
|
161
|
+
iteration_result = IterationResult(
|
|
162
|
+
success=False, text=None, error=fatal_error, harness_run_id=""
|
|
163
|
+
)
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
traceback.print_exc()
|
|
166
|
+
iteration_result = IterationResult(
|
|
167
|
+
success=False, text=None, error=str(exc), harness_run_id=""
|
|
168
|
+
)
|
|
169
|
+
write_iteration_artifacts(
|
|
170
|
+
iteration_dir=iteration_dir,
|
|
171
|
+
rendered_prompt=rendered_prompt,
|
|
172
|
+
iteration_result=iteration_result,
|
|
173
|
+
)
|
|
174
|
+
finished_request = FinishedRequest(
|
|
175
|
+
session_id=task.session_id,
|
|
176
|
+
workflow_id=task.workflow_id,
|
|
177
|
+
iteration=task.iteration,
|
|
178
|
+
success=iteration_result.success,
|
|
179
|
+
text=iteration_result.text,
|
|
180
|
+
error=iteration_result.error,
|
|
181
|
+
)
|
|
182
|
+
pending_path = _write_pending_finished_request(
|
|
183
|
+
repo_root=repo_root, request=finished_request
|
|
184
|
+
)
|
|
185
|
+
finished_assignment = FinishedAssignment(
|
|
186
|
+
request=finished_request, pending_path=pending_path
|
|
187
|
+
)
|
|
188
|
+
if fatal_error is not None:
|
|
189
|
+
raise FatalAssignmentError(
|
|
190
|
+
finished_assignment=finished_assignment, message=fatal_error
|
|
191
|
+
)
|
|
192
|
+
return finished_assignment
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _write_pending_finished_request(
|
|
196
|
+
*, repo_root: Path, request: FinishedRequest
|
|
197
|
+
) -> Path:
|
|
198
|
+
path = pending_finished_request_path(
|
|
199
|
+
repo_root=repo_root,
|
|
200
|
+
session_id=request.session_id,
|
|
201
|
+
iteration=request.iteration,
|
|
202
|
+
workflow_id=request.workflow_id,
|
|
203
|
+
)
|
|
204
|
+
path.write_text(json.dumps(request.model_dump(), indent=2), encoding="utf-8")
|
|
205
|
+
return path
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _clear_pending_finished_request(*, path: Path) -> None:
|
|
209
|
+
path.unlink(missing_ok=True)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _render_prompt(
|
|
213
|
+
*,
|
|
214
|
+
config_snapshot: RootConfigSnapshot,
|
|
215
|
+
session_id: str,
|
|
216
|
+
workflow_set: str,
|
|
217
|
+
iteration: int,
|
|
218
|
+
workflow_id: str,
|
|
219
|
+
iteration_dir: Path,
|
|
220
|
+
harness_output_root: Path,
|
|
221
|
+
workflow_prompt: str,
|
|
222
|
+
emits_goal_check: bool = False,
|
|
223
|
+
repo_root: Path | None = None,
|
|
224
|
+
) -> str:
|
|
225
|
+
root = repo_root or Path.cwd()
|
|
226
|
+
session_dir = session_dir_path(repo_root=root, session_id=session_id)
|
|
227
|
+
lines = [
|
|
228
|
+
"loopy-loop assignment",
|
|
229
|
+
"",
|
|
230
|
+
f"Goal: {config_snapshot.goal}",
|
|
231
|
+
"Completion criteria:",
|
|
232
|
+
*[f"- {item}" for item in config_snapshot.completion_criteria],
|
|
233
|
+
"Stop criteria:",
|
|
234
|
+
*[f"- {item}" for item in config_snapshot.stop_criteria],
|
|
235
|
+
"",
|
|
236
|
+
f"Session ID: {session_id}",
|
|
237
|
+
f"Workflow set: {workflow_set}",
|
|
238
|
+
f"Iteration: {iteration}",
|
|
239
|
+
f"Workflow ID: {workflow_id}",
|
|
240
|
+
f"Session directory: {session_dir}",
|
|
241
|
+
f"Session goal path: {session_goal_path(repo_root=root, session_id=session_id)}",
|
|
242
|
+
"Session project_state directory: "
|
|
243
|
+
f"{project_state_dir_path(repo_root=root, session_id=session_id)}",
|
|
244
|
+
"Session eval_checks directory: "
|
|
245
|
+
f"{eval_checks_dir_path(repo_root=root, session_id=session_id)}",
|
|
246
|
+
"Session updates_from_user path: "
|
|
247
|
+
f"{updates_from_user_path(repo_root=root, session_id=session_id)}",
|
|
248
|
+
"Session child_requests directory: "
|
|
249
|
+
f"{child_requests_dir_path(repo_root=root, session_id=session_id)}",
|
|
250
|
+
f"Session control path: {control_path(repo_root=root, session_id=session_id)}",
|
|
251
|
+
"Session finished ledger path: "
|
|
252
|
+
f"{finished_path(repo_root=root, session_id=session_id)}",
|
|
253
|
+
"Session harness outputs directory: "
|
|
254
|
+
f"{harness_outputs_dir_path(repo_root=root, session_id=session_id)}",
|
|
255
|
+
f"Iteration directory: {iteration_dir}",
|
|
256
|
+
f"Iteration harness output root: {harness_output_root}",
|
|
257
|
+
]
|
|
258
|
+
if session_dir.parent.name == "children":
|
|
259
|
+
lines.append(f"Parent session directory: {session_dir.parent.parent}")
|
|
260
|
+
if workflow_id == "goal_check" or emits_goal_check:
|
|
261
|
+
lines.append(
|
|
262
|
+
f"goal_check.json output path: {iteration_dir / GOAL_CHECK_FILENAME}"
|
|
263
|
+
)
|
|
264
|
+
lines.extend(["", "Workflow body:", workflow_prompt])
|
|
265
|
+
return "\n".join(lines).rstrip() + "\n"
|