ace-assign 0.53.4 → 0.55.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/split-subtree-root.step.yml +4 -2
  3. data/.ace-defaults/assign/config.yml +1 -0
  4. data/.ace-defaults/assign/presets/work-on-task.yml +3 -0
  5. data/CHANGELOG.md +15 -0
  6. data/docs/demo/fork-provider.cast +814 -937
  7. data/docs/demo/fork-provider.gif +0 -0
  8. data/docs/demo/fork-provider.recording.json +15 -17
  9. data/docs/demo/fork-provider.tape.yml +16 -4
  10. data/docs/usage.md +30 -7
  11. data/handbook/guides/fork-context.g.md +29 -5
  12. data/handbook/skills/as-assign-drive/SKILL.md +2 -2
  13. data/handbook/workflow-instructions/assign/drive.wf.md +109 -36
  14. data/handbook/workflow-instructions/assign/prepare.wf.md +5 -0
  15. data/lib/ace/assign/atoms/preset_expander.rb +4 -0
  16. data/lib/ace/assign/atoms/tree_formatter.rb +2 -2
  17. data/lib/ace/assign/cli/commands/add.rb +20 -11
  18. data/lib/ace/assign/cli/commands/assignment_target.rb +49 -18
  19. data/lib/ace/assign/cli/commands/create.rb +1 -1
  20. data/lib/ace/assign/cli/commands/fail.rb +1 -1
  21. data/lib/ace/assign/cli/commands/finish.rb +26 -5
  22. data/lib/ace/assign/cli/commands/fork_run.rb +56 -17
  23. data/lib/ace/assign/cli/commands/list.rb +4 -3
  24. data/lib/ace/assign/cli/commands/retry_cmd.rb +1 -1
  25. data/lib/ace/assign/cli/commands/status.rb +60 -34
  26. data/lib/ace/assign/cli/commands/step.rb +4 -4
  27. data/lib/ace/assign/cli.rb +1 -1
  28. data/lib/ace/assign/models/assignment_info.rb +33 -4
  29. data/lib/ace/assign/models/queue_state.rb +101 -39
  30. data/lib/ace/assign/models/step.rb +13 -3
  31. data/lib/ace/assign/molecules/fork_session_launcher.rb +76 -60
  32. data/lib/ace/assign/molecules/step_writer.rb +3 -3
  33. data/lib/ace/assign/molecules/tmux_control_surface_runner.rb +249 -0
  34. data/lib/ace/assign/organisms/assignment_executor.rb +132 -82
  35. data/lib/ace/assign/version.rb +1 -1
  36. data/lib/ace/assign.rb +1 -0
  37. metadata +17 -3
  38. data/lib/ace/assign/molecules/tmux_fork_runner.rb +0 -191
Binary file
@@ -1,29 +1,27 @@
1
1
  {
2
2
  "backend": "asciinema",
3
- "tape_path": "/home/mc/ace-t.u53.0/ace-assign/docs/demo/fork-provider.tape.yml",
4
- "cast_path": "/home/mc/ace-t.u53.0/ace-assign/docs/demo/fork-provider.cast",
5
- "visual_path": "/home/mc/ace-t.u53.0/ace-assign/docs/demo/fork-provider.gif",
6
- "sandbox_path": "/home/mc/ace-t.u53.0/.ace-local/demo/sandbox/8rekyn",
3
+ "tape_path": "/home/mc/ace-t.n1d/ace-assign/docs/demo/fork-provider.tape.yml",
4
+ "cast_path": "/home/mc/ace-t.n1d/ace-assign/docs/demo/fork-provider.cast",
5
+ "visual_path": "/home/mc/ace-t.n1d/ace-assign/docs/demo/fork-provider.gif",
6
+ "sandbox_path": "/home/mc/ace-t.n1d/.ace-local/demo/sandbox/8rfe9v",
7
7
  "verification": {
8
- "status": "scenario-defect",
9
- "classification": "scenario_defect",
10
- "summary": "Recording scenario failed verification",
11
- "retryable": true,
12
- "report_path": "/home/mc/ace-t.u53.0/.ace-local/demo/fork-provider-error-report.md",
8
+ "status": "pass",
9
+ "classification": "pass",
10
+ "summary": "Verification passed",
11
+ "retryable": false,
12
+ "report_path": null,
13
13
  "details": {
14
- "cast_path": "/home/mc/ace-t.u53.0/ace-assign/docs/demo/fork-provider.cast",
14
+ "cast_path": "/home/mc/ace-t.n1d/ace-assign/docs/demo/fork-provider.cast",
15
15
  "inputs_recorded": 0,
16
- "echoed_commands_recorded": 70,
16
+ "echoed_commands_recorded": 435,
17
17
  "script_commands_recorded": 0,
18
- "commands_expected": 7,
18
+ "commands_expected": 3,
19
19
  "captured_vars": {
20
- "ACE_TMUX_SESSION": "fork-demo PROJECT_ROOT_PATH=\"$PWD\" ace-assign fork-run --assignment \"$(cat ASSIGN_ID)@010\" --launch-mode tmux --provider codex:gpt@yolo --cli-args \"--no-alt-screen\" --timeout 120"
20
+ "ACE_TMUX_SESSION": "fork-demo PROJECT_ROOT_PATH=\"$PWD\" ace-assign status --assignment \"$(cat ASSIGN_ID)@010\""
21
21
  },
22
22
  "missing_vars": [],
23
- "missing_output": [
24
- "{\"Task context\" => nil}"
25
- ],
26
- "missing_output_sequence": null,
23
+ "missing_output": [],
24
+ "missing_output_sequence": [],
27
25
  "forbidden_hits": [],
28
26
  "assertion_failures": [],
29
27
  "assertions_skipped": false
@@ -34,13 +34,17 @@ setup:
34
34
  PROJECT_ROOT_PATH="$PWD" ace-assign create --yaml fork-demo-job.yaml
35
35
  ruby -e 'link = ".ace-local/assign/.latest"; abort "no latest assignment" unless File.symlink?(link); File.write("ASSIGN_ID", File.basename(File.readlink(link)))'
36
36
  - run: tmux new-session -d -s fork-demo -n work -c "$PWD" "bash --noprofile --norc -i"
37
- - run: |
38
- bash -lc 'sleep 45; tmux detach-client -s fork-demo || true' >/tmp/ace-demo-fork-provider-detach.log 2>&1 &
39
37
  scenes:
40
38
  - name: Attach to the operator work window
41
39
  commands:
42
- - type: tmux attach -t fork-demo
43
- sleep: 2s
40
+ - tmux:
41
+ action: attach
42
+ session: fork-demo
43
+ - tmux:
44
+ action: wait
45
+ for: window-active
46
+ session: fork-demo
47
+ window: work
44
48
  - type: tmux display-message -p '#W'
45
49
  sleep: 2s
46
50
  - name: Launch the fork into a visible tmux window
@@ -49,6 +53,14 @@ scenes:
49
53
  sleep: 2s
50
54
  - type: bash -lc 'ACE_TMUX_SESSION=fork-demo PROJECT_ROOT_PATH="$PWD" ace-assign fork-run --assignment "$(cat ASSIGN_ID)@010" --launch-mode tmux --provider codex:gpt@yolo --cli-args "--no-alt-screen" --timeout 120 &'
51
55
  sleep: 35s
56
+ - tmux:
57
+ action: wait
58
+ for: window-exists
59
+ session: fork-demo
60
+ window: work-fs
61
+ - tmux:
62
+ action: detach
63
+ session: fork-demo
52
64
  verify:
53
65
  require_output:
54
66
  - work
data/docs/usage.md CHANGED
@@ -4,8 +4,8 @@ title: ace-assign Usage Guide
4
4
  purpose: Complete command reference for ace-assign queue orchestration, hierarchy,
5
5
  and fork execution.
6
6
  ace-docs:
7
- last-updated: '2026-04-01'
8
- last-checked: '2026-04-01'
7
+ last-updated: '2026-04-23'
8
+ last-checked: '2026-04-23'
9
9
  ---
10
10
 
11
11
  # ace-assign Usage Guide
@@ -47,6 +47,7 @@ ace-test-suite --target all
47
47
  ```bash
48
48
  ace-assign create --yaml job.yaml
49
49
  ace-assign status
50
+ ace-assign start
50
51
  ace-assign step
51
52
  ace-assign finish --message step-010.md
52
53
  ace-assign status
@@ -57,6 +58,7 @@ Use scoped targeting when needed:
57
58
 
58
59
  ```bash
59
60
  ace-assign status --assignment abc123@010.01
61
+ ace-assign start --assignment abc123@010.01
60
62
  ace-assign finish --message done.md --assignment abc123@010.01
61
63
  ```
62
64
 
@@ -118,6 +120,7 @@ Text modes:
118
120
  - `compact` (default) prints a short summary, hidden-step stats, and up to 5 upcoming step lines
119
121
  - `progress` prints a single summary line
120
122
  - `full` prints the full tree/table without step instructions
123
+ - JSON emits `active_steps` for all active steps in scope and `next_step` only when no step is active in that scope
121
124
 
122
125
  HITL stall behavior:
123
126
 
@@ -133,7 +136,7 @@ HITL stall behavior:
133
136
 
134
137
  ### `ace-assign step [STEP]`
135
138
 
136
- Show instructions for the current in-progress step, the next workable step, or an explicit step number.
139
+ Show instructions for the deepest active step in scope, the next workable pending step when nothing is active, or an explicit step number.
137
140
 
138
141
  Options:
139
142
 
@@ -143,7 +146,7 @@ Options:
143
146
 
144
147
  ### `ace-assign start [STEP]`
145
148
 
146
- Start next workable pending step, or an explicit pending step in the active assignment.
149
+ Mark the next workable pending step active, or mark an explicit pending step active in the targeted assignment or subtree.
147
150
 
148
151
  Options:
149
152
 
@@ -153,11 +156,11 @@ Options:
153
156
 
154
157
  ### `ace-assign finish [STEP] --message VALUE`
155
158
 
156
- Complete current in-progress step (or explicit step in active assignment) with report content.
159
+ Complete the current active step (or explicit active step in the active assignment) with report content.
157
160
  Use positional `STEP` only for the active assignment. When targeting another
158
161
  assignment or a scoped subtree, pass `--assignment <id>` or
159
162
  `--assignment <id@step>` without a positional `STEP`; the command finishes the
160
- current in-progress step in that target.
163
+ deepest active step in that target.
161
164
 
162
165
  `--message` accepts:
163
166
 
@@ -222,6 +225,7 @@ Options:
222
225
  - `--cli-args <args>`
223
226
  - `--timeout <seconds>`
224
227
  - `--launch-mode auto|headless|tmux`
228
+ - `--callback`
225
229
  - `--quiet, -q`
226
230
  - `--debug, -d`
227
231
 
@@ -229,7 +233,25 @@ Launch modes:
229
233
 
230
234
  - `auto` (default): use tmux when the current process is already inside tmux or `ACE_TMUX_SESSION` is set; otherwise use the headless subprocess path
231
235
  - `headless`: force the existing provider subprocess path and never create tmux panes
232
- - `tmux`: require tmux context, create or reuse `<current-window>-fs`, start a real interactive agent in a pane there via `ace-llm --interactive`, and send the scoped `/as-assign-drive <assignment>@<root>` handoff automatically
236
+ - `tmux`: require tmux context, create or reuse `<origin-window>-fs`, start a real interactive agent in a pane there via `ace-llm --interactive`, and send the scoped `/as-assign-drive <assignment>@<root>` handoff automatically. The fork window name uses the shared `ace-tmux` safe-name policy, so punctuation in the base window is replaced with `-`. Fork windows and panes are created detached, so the current tmux focus stays where the user left it.
237
+ - `tmux`: require tmux context, create or reuse `<origin-window>-fs`, start a real interactive agent in a pane there via `ace-llm --interactive`, and send the scoped `/as-assign-drive <assignment>@<root>` handoff automatically
238
+ - This mode consumes the shared `ace-tmux` runtime/control surface for tmux targeting, pane dispatch, and diagnostics.
239
+ - The fork window name uses the shared `ace-tmux` safe-name policy, so punctuation in the base window is replaced with `-`.
240
+ - Fork windows and panes are created detached, so the current tmux focus stays where the user left it.
241
+ - Assignment step state remains the source of truth for subtree completion or failure; pane capture is diagnostic support only.
242
+
243
+ Callback mode:
244
+
245
+ - `--callback`: tmux-only fork mode that captures the pane where `fork-run` was started and passes it into the child fork session as `ACE_ASSIGN_CALLBACK_PANE`
246
+ - In callback mode the child agent is instructed to send one final status sentence back to the origin pane with `ace-tmux send` before stopping
247
+ - Callback mode is intended for interactive parent/child agent tmux flows where the parent stays idle until the child sends the final message back
248
+
249
+ Launch-mode precedence for fork execution:
250
+
251
+ 1. CLI `--launch-mode`
252
+ 2. Step frontmatter `fork.mode`
253
+ 3. Config `execution.launch_mode`
254
+ 4. Built-in default `auto`
233
255
 
234
256
  Provider resolution precedence for fork execution:
235
257
 
@@ -247,6 +269,7 @@ status: pending
247
269
  context: fork
248
270
  fork:
249
271
  provider: "claude:sonnet@yolo"
272
+ mode: "tmux"
250
273
  ---
251
274
  ```
252
275
 
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  doc-type: guide
3
3
  title: Fork Context Guide
4
- purpose: Explain fork context execution model, boundaries, and recovery patterns for ace-assign subtree delegation.
4
+ purpose: Explain fork context execution model, boundaries, and recovery patterns for
5
+ ace-assign subtree delegation.
5
6
  ace-docs:
6
- last-updated: 2026-03-18
7
- last-checked: 2026-03-21
7
+ last-updated: '2026-04-23'
8
+ last-checked: '2026-04-23'
8
9
  ---
9
10
 
10
11
  # Fork Context Guide
@@ -13,13 +14,14 @@ ace-docs:
13
14
 
14
15
  Fork context enables step files to run in isolated agent contexts using the Task tool. When a step has `context: fork` in its frontmatter, ace-assign outputs instructions for the orchestrating agent to execute the step via a subagent.
15
16
 
16
- You can also set a per-step provider override with `fork.provider`:
17
+ You can also set per-step launch overrides with `fork.provider` and `fork.mode`:
17
18
 
18
19
  ```yaml
19
20
  ---
20
21
  context: fork
21
22
  fork:
22
23
  provider: "claude:sonnet@yolo"
24
+ mode: "tmux"
23
25
  ---
24
26
  ```
25
27
 
@@ -30,6 +32,13 @@ Provider precedence during `ace-assign fork-run`:
30
32
  3. Assign config `execution.provider`
31
33
  4. Built-in default
32
34
 
35
+ Launch-mode precedence during `ace-assign fork-run`:
36
+
37
+ 1. CLI `--launch-mode`
38
+ 2. Step `fork.mode`
39
+ 3. Assign config `execution.launch_mode`
40
+ 4. Built-in default `auto`
41
+
33
42
  For hierarchical split workflows, use **parent-only** fork markers:
34
43
  - Split parent step: `context: fork`
35
44
  - Child steps (`onboard-base`, `task-load`, `plan-task`, `work-on-task`, `verify-test`, `release-minor`): no `context: fork`
@@ -127,6 +136,21 @@ The orchestrating agent:
127
136
  - Processes subagent reports
128
137
  - Handles failures and retries
129
138
 
139
+ ## Scoped Status Semantics
140
+
141
+ Fork execution uses two layers of active ownership:
142
+
143
+ - The fork root stays `active` while the delegated session owns that subtree.
144
+ - Inside that subtree, zero or one deepest started descendant may also be `active`.
145
+
146
+ Status reporting is scope-aware:
147
+
148
+ - Unscoped status lists all `active_steps` in queue order and hides pending descendants under an active fork root from global `next_step` selection.
149
+ - Scoped status (`--assignment <id>@<root>`) reports activity only inside that subtree.
150
+ - When nothing is active inside the current scope, status reports `next_step` instead of predicting started work.
151
+
152
+ This keeps queue eligibility separate from execution state: pending descendants stay pending until scoped execution explicitly starts them.
153
+
130
154
  ## Recovery From Failed Fork Subtrees
131
155
 
132
156
  When a forked subtree fails, use **adaptive minimal-safe replay**:
@@ -245,4 +269,4 @@ ACE_DEBUG=1 ace-assign status
245
269
 
246
270
  - [ace-assign README](../../README.md) - Main documentation
247
271
  - [Work Queue Model](../workflow-instructions/drive-assignment.wf.md) - Assignment management
248
- - `ace-assign fork-run --root <step> --assignment <id>` - Prepare subtree-scoped fork session
272
+ - `ace-assign fork-run --root <step> --assignment <id>` - Prepare subtree-scoped fork session
@@ -12,7 +12,7 @@ allowed-tools:
12
12
  - AskUserQuestion
13
13
  - Skill
14
14
  argument-hint: "[assignment[@scope]]"
15
- last_modified: 2026-04-07
15
+ last_modified: 2026-04-23
16
16
  source: ace-assign
17
17
  skill:
18
18
  kind: workflow
@@ -29,7 +29,7 @@ Hard stop rule:
29
29
  - Do not stop while waiting on a forked subtree; keep polling and resume the parent drive loop as soon as the subtree reaches a terminal state.
30
30
  - Treat `ace-assign status --assignment <id>@<root>` as the source of truth for fork completion; quiet terminal output is not enough reason to stop or declare a stall.
31
31
  - If a prior terminal or drive session ended, re-enter from assignment state and continue from the next runnable work instead of depending on the old terminal handle.
32
- - Before any final response, re-check pinned assignment status. If any runnable `pending` or `in_progress` work remains, continue driving.
32
+ - Before any final response, re-check pinned assignment status. If any runnable `pending` or `active` work remains, continue driving.
33
33
  - If unrelated dirty files are generated side effects outside task scope, clean/reset them instead of auto-committing them.
34
34
  - Use progress updates for partial status only.
35
35
  - Return a final user-facing completion response only when the assignment is complete or the workflow reaches an explicit blocker/failure stop condition.
@@ -12,8 +12,8 @@ doc-type: workflow
12
12
  title: Drive Assignment Workflow
13
13
  purpose: workflow instruction for driving ace-assign assignment execution
14
14
  ace-docs:
15
- last-updated: 2026-04-12
16
- last-checked: 2026-03-21
15
+ last-updated: '2026-04-23'
16
+ last-checked: '2026-04-23'
17
17
  ---
18
18
 
19
19
  # Drive Assignment Workflow
@@ -25,7 +25,7 @@ Drive agent execution through an active assignment by continuously checking stat
25
25
  ## Prerequisites
26
26
 
27
27
  - An active assignment exists (created via `ace-assign create` or `/as-assign-create`)
28
- - Assignment has at least one pending or in_progress step
28
+ - Assignment has at least one pending or active step
29
29
 
30
30
  ## Assignment Context Propagation
31
31
 
@@ -36,11 +36,11 @@ When working with multiple concurrent assignments, the active assignment is reso
36
36
  3. `.latest` symlink (auto-updated on any activity)
37
37
  4. Scan all assignments (fallback)
38
38
 
39
- If this workflow is invoked with an argument (for example `/as-assign-drive abc123@010.01`), treat that value as the initial assignment target. If no argument is provided, resolve one active assignment and pin it for the entire loop.
39
+ If this workflow is invoked with an argument (for example `/as-assign-drive abc123@010.01`), treat that exact value as the initial assignment target. If no argument is provided, resolve one active assignment and pin it for the entire loop.
40
40
 
41
41
  ```bash
42
- # Set once from workflow argument (empty when not provided)
43
- ASSIGNMENT_TARGET="${1:-}"
42
+ # Set once from workflow argument or internal scoped default (empty when neither is provided)
43
+ ASSIGNMENT_TARGET="${1:-${ACE_ASSIGN_DEFAULT_TARGET:-}}"
44
44
 
45
45
  # Resolve and pin assignment identity for the full drive loop
46
46
  if [ -n "$ASSIGNMENT_TARGET" ]; then
@@ -59,6 +59,8 @@ if [ -z "$ASSIGNMENT_TARGET" ]; then
59
59
  fi
60
60
  ```
61
61
 
62
+ `ACE_ASSIGN_DEFAULT_TARGET` is an internal fallback passed by `fork-run` launches. If it is present, use it exactly. Do not widen it back to the parent assignment, do not drop the `@<root>` scope suffix, and do not re-resolve an unscoped assignment target later in the loop.
63
+
62
64
  ### Explicit Assignment Targeting (Recommended)
63
65
 
64
66
  Use explicit flags to propagate assignment context across subprocesses and tools:
@@ -81,7 +83,7 @@ ace-assign status --assignment abc123@010.01
81
83
  ace-assign finish --message done.md --assignment abc123@010.01
82
84
  ```
83
85
 
84
- When an assignment target includes scope (`<id>@<root>`), `ace-assign finish --message` advances only within that subtree and stops when the subtree is complete.
86
+ When an assignment target includes scope (`<id>@<root>`), `ace-assign finish --message` finishes only within that subtree. Pending descendants remain pending until scoped execution explicitly starts them.
85
87
 
86
88
  Helper command:
87
89
 
@@ -115,7 +117,7 @@ Repeat the following cycle until all steps are done or failed:
115
117
 
116
118
  ### Run-Until-Blocked Contract
117
119
 
118
- Once `ASSIGNMENT_TARGET` is pinned, keep driving the same assignment until exactly one of these stop conditions is true:
120
+ Once `ASSIGNMENT_TARGET` is pinned, keep driving that exact target until exactly one of these stop conditions is true:
119
121
 
120
122
  1. `ace-assign status --assignment "$ASSIGNMENT_TARGET"` shows all steps complete
121
123
  2. A workflow step explicitly requires HITL or other user judgment before execution can continue
@@ -127,6 +129,7 @@ Do **not** stop merely because you have useful progress to report.
127
129
  - Intermediate progress belongs in short progress updates, not in a final completion response.
128
130
  - `pending` steps with no active step are not a stop condition. They mean the queue must be advanced and the loop must continue.
129
131
  - A batch child subtree finishing is not a completion boundary for the parent assignment.
132
+ - If `ASSIGNMENT_TARGET` includes `@<root>`, that scoped subtree is the entire execution boundary.
130
133
  - A paused assignment with remaining runnable work is not "done"; treat it as a recoverable scheduler state and resume the loop.
131
134
 
132
135
  ### Final Response Gate
@@ -139,20 +142,21 @@ ace-assign status --assignment "$ASSIGNMENT_TARGET" --format json
139
142
 
140
143
  You may only stop and send a final response when one of these is true:
141
144
 
142
- - the pinned assignment has no remaining runnable `pending` or `in_progress` work
145
+ - the pinned assignment has no remaining runnable `pending` or `active` work
143
146
  - the workflow recorded an explicit blocker or unrecoverable failure stop condition
144
147
  - the user explicitly interrupted or canceled execution
145
148
 
146
149
  Do **not** send a final response merely because:
147
150
 
148
- - one child subtree completed
151
+ - one child subtree completed in the parent assignment
149
152
  - useful progress was made
150
153
  - a prior terminal session ended
151
- - the parent assignment auto-advanced to the next active step
154
+ - the parent assignment surfaced the next pending step after active work finished
152
155
 
153
156
  Concrete example:
154
157
 
155
- - `010.01 done` and `010.02.01 in_progress` means continue driving the assignment. It is not a completion boundary.
158
+ - `010.01 done` and `010.02.01 active` means continue driving the assignment. It is not a completion boundary.
159
+ - `040.01 done`, `040.02 done`, and `040.03 done` under `/as-assign-drive <id>@040` means stop the scoped worker. Do not inspect parent step `070` from that scoped process.
156
160
 
157
161
  ### Step Execution Policy
158
162
 
@@ -216,7 +220,7 @@ echo "$STATUS_OUTPUT"
216
220
  Read the output to identify:
217
221
 
218
222
  - Assignment ID (must remain equal to pinned `ASSIGNMENT_ID`)
219
- - Current step number, name, and status
223
+ - Active step list in scope, or next step when nothing is active
220
224
  - Remaining visible steps in the queue preview
221
225
  - Hidden-step counts for large queues
222
226
 
@@ -264,7 +268,7 @@ Dirty-tree classification rule:
264
268
 
265
269
  Example:
266
270
 
267
- - A bulk untracked `.ace/...` tree created by `ace-config init` is generated side-effect output. Clean it; do not create a "pre-fork" commit for it.
271
+ - A bulk untracked `.ace/...` tree created by `ace-config sync` is generated side-effect output. Clean it; do not create a "pre-fork" commit for it.
268
272
 
269
273
  #### Delegation Rule
270
274
 
@@ -293,8 +297,15 @@ ace-assign fork-run --assignment <id>@020
293
297
  **Delegation boundary rule**
294
298
 
295
299
  - Outside a delegated fork scope, do NOT execute fork steps inline.
300
+ - First decide whether the fork boundary is already entered before issuing `fork-run`.
296
301
  - If scoped status for `--assignment <id>@<root>` already resolves work inside `<root>`, the fork boundary is already entered: continue inline and never call `fork-run` again for the same `<root>`.
297
- - If the current step is a top-level step with `FORK: yes` and no matching scope is active, delegate immediately.
302
+ - A same-root call to `ace-assign fork-run --assignment <id>@<root>` from inside that exact scoped subtree is invalid and must be treated as an error, not a retry path.
303
+ - If the scoped root itself is active and no child in that subtree is active yet, run:
304
+
305
+ - `ace-assign start --assignment "$ASSIGNMENT_TARGET"`
306
+
307
+ - After a child step inside the scoped subtree becomes active, continue executing that child inline within the same scope.
308
+ - Only if the current step is a top-level step with `FORK: yes` and no matching scope is already active should the driver delegate via `fork-run`.
298
309
 
299
310
  #### Nested Batch Containers (Container → Fork Children)
300
311
 
@@ -374,11 +385,29 @@ Conversational boundary rule:
374
385
  ```bash
375
386
  STATUS_JSON=$(ace-assign status --assignment "$ASSIGNMENT_TARGET" --format json)
376
387
  ASSIGNMENT_ID=$(echo "$STATUS_JSON" | ruby -rjson -e 'puts JSON.parse(STDIN.read).dig("assignment", "id")')
388
+ SCOPED_ROOT="${ASSIGNMENT_TARGET#*@}"
389
+ ACTIVE_STEP=$(echo "$STATUS_JSON" | ruby -rjson -e '
390
+ scoped_root = ARGV[0].to_s
391
+ active = Array(JSON.parse(STDIN.read)["active_steps"])
392
+ child = active.find { |step| step["number"] != scoped_root && step["number"].start_with?("#{scoped_root}.") }
393
+ puts(child ? child["number"] : active.first&.fetch("number", nil))
394
+ ' "$SCOPED_ROOT")
395
+
396
+ if [ -n "$SCOPED_ROOT" ] && [ "$ACTIVE_STEP" = "$SCOPED_ROOT" ]; then
397
+ ace-assign start --assignment "$ASSIGNMENT_TARGET"
398
+ continue
399
+ fi
400
+
377
401
  FORK_ROOT=$(echo "$STATUS_JSON" | ruby -rjson -e '
378
- p = JSON.parse(STDIN.read)["current_step"]
379
- puts p["number"] if p && p["context"] == "fork"
380
- ')
381
- if [ -n "$FORK_ROOT" ]; then
402
+ scoped_root = ARGV[0].to_s
403
+ active = Array(JSON.parse(STDIN.read)["active_steps"])
404
+ next_fork = active.find do |step|
405
+ step["context"] == "fork" &&
406
+ (scoped_root.empty? || (step["number"] != scoped_root && !step["number"].start_with?("#{scoped_root}.")))
407
+ end
408
+ puts next_fork["number"] if next_fork
409
+ ' "$SCOPED_ROOT")
410
+ if [ -n "$FORK_ROOT" ] && [ "$FORK_ROOT" != "$SCOPED_ROOT" ]; then
382
411
  ace-assign fork-run --assignment "${ASSIGNMENT_ID}@${FORK_ROOT}"
383
412
  # Re-check status after subtree delegation completes
384
413
  continue
@@ -391,6 +420,14 @@ fi
391
420
 
392
421
  After launching `ace-assign fork-run`, the driver remains inside the same drive session.
393
422
 
423
+ - If fork session metadata includes `callback_pane`, callback mode is active. In that case:
424
+
425
+ - do not poll the forked subtree on a timer
426
+ - do not run the `sleep 360` loop
427
+ - remain idle after launch and wait for the child forked agent to send a final status message back into the origin pane
428
+ - treat the callback message as the trigger to resume the parent drive loop
429
+ - if the callback never arrives and the session is resumed later, recover from assignment state and scoped status instead of waiting forever
430
+
394
431
  - Treat "fork subtree is still running" as an internal progress state, not as a stop condition.
395
432
  - Do not end the turn, emit a final user-facing completion summary, or hand control back to the user merely because the driver is waiting on fork completion.
396
433
  - Poll the forked subtree every 6 minutes by default. Use two signals on each poll:
@@ -409,6 +446,26 @@ After launching `ace-assign fork-run`, the driver remains inside the same drive
409
446
  - A quiet terminal is not a stall by itself. Only treat the fork as stalled when there is no scoped status movement, no new subtree reports, and no process exit for about 30 minutes.
410
447
  - When the wait ends, immediately re-enter the parent drive loop. Do not stop between "fork finished" and "next runnable step started."
411
448
 
449
+ #### Fork Callback Rule
450
+
451
+ When `ACE_ASSIGN_CALLBACK_PANE` is present in the forked child environment:
452
+
453
+ - before stopping for either success or failure, send one final sentence back to the origin pane with direct `ace-tmux send`
454
+ - use the exact pane target from `ACE_ASSIGN_CALLBACK_PANE`
455
+ - use a success sentence shaped like:
456
+
457
+ ```bash
458
+ ace-tmux send --pane "$ACE_ASSIGN_CALLBACK_PANE" --msg "Fork subtree ${FORK_ROOT} for assignment ${ASSIGNMENT_ID} completed. Resume parent assignment drive now." --key Enter
459
+ ```
460
+
461
+ - use a failure sentence shaped like:
462
+
463
+ ```bash
464
+ ace-tmux send --pane "$ACE_ASSIGN_CALLBACK_PANE" --msg "Fork subtree ${FORK_ROOT} for assignment ${ASSIGNMENT_ID} failed. Resume parent assignment drive and inspect scoped status." --key Enter
465
+ ```
466
+
467
+ - do not invent a new skill or wrapper for this callback; use `ace-tmux send` directly
468
+
412
469
  #### Post-Fork Resume Checklist
413
470
 
414
471
  Immediately after the fork wait ends, run this checklist in order:
@@ -423,9 +480,20 @@ Immediately after the fork wait ends, run this checklist in order:
423
480
  ace-assign start --assignment "$ASSIGNMENT_TARGET"
424
481
  ```
425
482
 
426
- 6. If pending or `in_progress` work remains and no blocker was recorded, continue the main loop immediately.
483
+ 6. If pending or `active` work remains and no blocker was recorded, continue the main loop immediately.
427
484
  7. Only stop if the assignment now satisfies a real stop condition from [Run-Until-Blocked Contract](#run-until-blocked-contract).
428
485
 
486
+ #### Scoped Worker Completion Boundary
487
+
488
+ When `ASSIGNMENT_TARGET` already includes `@<root>`, the current process is the subtree worker, not the parent orchestrator.
489
+
490
+ - After report review, re-check `ace-assign status --assignment "$ASSIGNMENT_TARGET"`.
491
+ - If that scoped status is terminal, stop immediately.
492
+ - Do not query the parent assignment from that scoped worker.
493
+ - Do not widen a scoped target back to parent assignment status.
494
+ - Do not continue into later sibling roots such as `070` from `/as-assign-drive <id>@040`.
495
+ - Parent resume after subtree completion belongs only to the unscoped driver that launched the fork.
496
+
429
497
  Detached-resume rule:
430
498
 
431
499
  - If a prior drive session or terminal ended, a new `/as-assign-drive` invocation MUST recover from assignment state, not from the old terminal handle.
@@ -509,7 +577,7 @@ After fork-run returns and completion is verified, the driver acts as the **guar
509
577
 
510
578
  After all fork subtrees within a batch container complete, the container auto-marks as Done. However, the queue pointer may not automatically advance to the next top-level step.
511
579
 
512
- **After verifying all fork subtree reports**, if `ace-assign status` shows no Active step (all completed steps but no new in-progress step), run:
580
+ **After verifying all fork subtree reports**, if `ace-assign status` shows no active step (all completed steps but no new active step), run:
513
581
 
514
582
  ```bash
515
583
  ace-assign start --assignment "$ASSIGNMENT_TARGET"
@@ -717,7 +785,7 @@ Creates a new step linked to the original. Original remains visible as failed.
717
785
  ace-assign add "fix-issue" --instructions "Fix the failing tests and verify" --assignment "$ASSIGNMENT_TARGET"
718
786
  ```
719
787
 
720
- New step is inserted after the current in-progress step.
788
+ New step is inserted after the current active step.
721
789
 
722
790
  When the failure evidence is E2E-specific (`ace-test-e2e`, scenario IDs, or `.ace-local/test-e2e/` artifacts), prefer:
723
791
 
@@ -748,8 +816,8 @@ echo "$FINAL_STATUS"
748
816
 
749
817
  Required checks:
750
818
 
751
- - If any steps remain `pending` or `in_progress` and no explicit blocker was recorded, resume the loop instead of stopping.
752
- - If the assignment is `paused`, `current_step` is `null`, and pending steps remain, run:
819
+ - If any steps remain `pending` or `active` and no explicit blocker was recorded, resume the loop instead of stopping.
820
+ - If the assignment is `paused`, `active_steps` is empty, and `next_step` is present, run:
753
821
 
754
822
  ```bash
755
823
  ace-assign start --assignment "$ASSIGNMENT_TARGET"
@@ -832,7 +900,7 @@ When executing a step with a `skill:` field:
832
900
  | Status | Meaning | Next Action |
833
901
  |--------|---------|-------------|
834
902
  | `pending` | Step not started | Cannot execute (wait for queue) |
835
- | `in_progress` | Step is active | Execute this step |
903
+ | `active` | Step is active | Execute this step |
836
904
  | `done` | Step completed | Move to next step |
837
905
  | `failed` | Step failed | Decide: retry, add fix, or abort |
838
906
 
@@ -846,11 +914,11 @@ When executing a step with a `skill:` field:
846
914
  │ ├── assignment.yaml # Assignment metadata
847
915
  │ ├── steps/ # Step files (.st.md extension)
848
916
  │ │ ├── 010-init.st.md # done
849
- │ │ ├── 020-implement.st.md # in_progress
917
+ │ │ ├── 020-implement.st.md # active
850
918
  │ │ └── 030-test.st.md # pending
851
919
  │ └── reports/ # Report files (.r.md extension)
852
920
  │ ├── 010-init.r.md # completed report
853
- │ └── 020-implement.r.md # in-progress report
921
+ │ └── 020-implement.r.md # active-step report
854
922
  └── def456/
855
923
  ├── assignment.yaml
856
924
  ├── steps/
@@ -897,28 +965,33 @@ $ ASSIGNMENT_TARGET=8or5kx
897
965
 
898
966
  # 1. Check status
899
967
  $ ace-assign status --assignment "$ASSIGNMENT_TARGET"
900
- Step 010: onboard [in_progress]
968
+ Next: 010 onboard
969
+
970
+ # 2. Start the next pending step
971
+ $ ace-assign start --assignment "$ASSIGNMENT_TARGET"
972
+ [Step 010 becomes active]
901
973
 
902
- # 2. Execute step (has skill: onboard)
974
+ # 3. Execute step (has skill: onboard)
903
975
  $ /as-onboard
904
976
  [Onboarding workflow runs...]
905
977
 
906
- # 3. Write report
978
+ # 4. Write report
907
979
  $ ace-assign finish --message onboard-complete.md --assignment "$ASSIGNMENT_TARGET"
908
- Step 010 marked done, advancing to 020
980
+ [Step 010 marked done]
909
981
 
910
- # 4. Check status again
982
+ # 5. Check status again
911
983
  $ ace-assign status --assignment "$ASSIGNMENT_TARGET"
912
- Step 020: work-on-task [in_progress]
984
+ Next: 020 work-on-task
913
985
 
914
- # 5. Execute next step (has skill: as-task-work)
986
+ # 6. Start and execute next step (has skill: as-task-work)
987
+ $ ace-assign start --assignment "$ASSIGNMENT_TARGET"
915
988
  $ /as-task-work 148
916
989
  [Task workflow runs...]
917
990
 
918
- # 6. Report and continue...
991
+ # 7. Report and continue...
919
992
  $ ace-assign finish --message task-done.md --assignment "$ASSIGNMENT_TARGET"
920
993
 
921
- # 7. Eventually...
994
+ # 8. Eventually...
922
995
  $ ace-assign status --assignment "$ASSIGNMENT_TARGET"
923
996
  All steps complete!
924
997
  ```
@@ -141,6 +141,11 @@ For `--taskrefs 148,149,150`, expansion generates:
141
141
  030 finalize
142
142
  ```
143
143
 
144
+ Generated batch parents should carry scheduler metadata:
145
+
146
+ - `010 batch-tasks (parent, auto-completes, batch_parent: true, parallel: false)`
147
+ - `fork_retry_limit: 1`
148
+
144
149
  The hierarchical numbering enables:
145
150
  - Parent auto-completion when all children are done
146
151
  - Parent-only fork markers for subtree delegation
@@ -192,6 +192,10 @@ module Ace
192
192
 
193
193
  step["skill"] = config["skill"] if config["skill"]
194
194
  step["context"] = config["context"] if config["context"]
195
+ step["batch_parent"] = config["batch_parent"] unless config["batch_parent"].nil?
196
+ step["parallel"] = config["parallel"] unless config["parallel"].nil?
197
+ step["max_parallel"] = config["max_parallel"] if config["max_parallel"]
198
+ step["fork_retry_limit"] = config["fork_retry_limit"] unless config["fork_retry_limit"].nil?
195
199
 
196
200
  step
197
201
  end
@@ -14,14 +14,14 @@ module Ace
14
14
  # # +-- onboard (completed)
15
15
  # # +-- work-on-task (running)
16
16
  # # | +-- onboard (completed)
17
- # # | +-- implement (in_progress)
17
+ # # | +-- implement (active)
18
18
  # # | \-- verify-tests (pending)
19
19
  # # \-- review-pr (pending)
20
20
  module TreeFormatter
21
21
  # State display labels matching list command
22
22
  STATE_LABELS = {
23
23
  pending: "pending",
24
- in_progress: "in_progress",
24
+ active: "active",
25
25
  running: "running",
26
26
  paused: "paused",
27
27
  completed: "completed",