orch: require explicit dispatch execution mode
This commit is contained in:
@@ -70,11 +70,11 @@ It should not be implemented as core `orch` runtime behavior because worker laun
|
||||
|
||||
For code tasks, execution should be isolated from the user's primary checkout.
|
||||
|
||||
- `orch dispatch` should create a task-attempt worktree
|
||||
- `orch dispatch --execution-mode code` should create a task-attempt worktree
|
||||
- the assigned worktree path should be stored in attempt metadata and inbox task payload
|
||||
- the worker runtime should execute inside that worktree
|
||||
- strict mode should require a committed base revision
|
||||
- non-code tasks may stay on a thread-only dispatch path with no worktree, but they still require a separate worker runtime to claim the inbox thread
|
||||
- `orch dispatch --execution-mode analysis` should stay on a thread-only path with no worktree, but it still requires a separate worker runtime to claim the inbox thread
|
||||
|
||||
See [worktree-execution.md](/home/kurihada/project/ai-workflow-skill/docs/worktree-execution.md) for the full lifecycle.
|
||||
|
||||
|
||||
+19
-11
@@ -53,12 +53,12 @@ Those belong to `inbox`.
|
||||
|
||||
## Workspace Model
|
||||
|
||||
For code-writing tasks, `orch` should allocate one Git worktree per attempt.
|
||||
For `--execution-mode code`, `orch` should allocate one Git worktree per attempt.
|
||||
|
||||
Strict policy:
|
||||
|
||||
- dispatch from a concrete committed `base_ref`
|
||||
- fail dispatch if strict mode is enabled and the leader is implicitly relying on uncommitted state
|
||||
- fail dispatch if the leader is implicitly relying on uncommitted state
|
||||
- create a fresh worktree for every retry
|
||||
- do not let workers edit the user's primary checkout
|
||||
|
||||
@@ -180,23 +180,24 @@ Suggested flags:
|
||||
|
||||
- `--run RUN_ID`
|
||||
- `--task TASK_ID`
|
||||
- `--execution-mode analysis|code`
|
||||
- `--to AGENT`
|
||||
- `--repo-path PATH`
|
||||
- `--base-ref REF`
|
||||
- `--workspace-root PATH`
|
||||
- `--strict-worktree`
|
||||
- `--body TEXT`
|
||||
- `--body-file PATH`
|
||||
|
||||
Behavior:
|
||||
|
||||
- creates a new attempt
|
||||
- automatically enables strict worktree mode for code-like tasks inferred from task metadata when worktree flags are omitted
|
||||
- resolves the source repository from `--repo-path` or the current working directory
|
||||
- resolves a committed base revision
|
||||
- creates a branch and worktree for the attempt when the task writes code
|
||||
- requires the caller to choose `--execution-mode analysis|code`
|
||||
- in `analysis` mode, stays thread-only and does not allocate a worktree
|
||||
- in `code` mode, resolves the source repository from `--repo-path` or the current working directory
|
||||
- in `code` mode, resolves a committed base revision
|
||||
- in `code` mode, creates a branch and worktree for the attempt
|
||||
- creates or links an `inbox` thread
|
||||
- writes workspace metadata into attempt storage and task payload
|
||||
- writes `execution_mode` into the inbox task payload and writes workspace metadata for code tasks into attempt storage and task payload
|
||||
- moves the task to `dispatched`
|
||||
- does not start a worker runtime on its own
|
||||
|
||||
@@ -205,7 +206,7 @@ Integration note:
|
||||
- a higher-level Codex bridge may save this JSON output, render a worker brief, and then spawn a worker sub-agent
|
||||
- that bridge should remain outside the core `orch` runtime so the scheduling contract stays portable
|
||||
|
||||
Strict-mode recommendation:
|
||||
Code-mode recommendation:
|
||||
|
||||
- if `--base-ref` is omitted and the repository is clean, default to `HEAD`
|
||||
- if `--base-ref` is omitted and the repository is dirty, fail dispatch
|
||||
@@ -416,7 +417,7 @@ Default behavior:
|
||||
`orch` should be implemented as a control plane on top of `inbox`.
|
||||
|
||||
- `orch dispatch` writes the first `task` message into `inbox`
|
||||
- `orch dispatch` also writes worktree metadata for code tasks into the attempt record and inbox payload
|
||||
- `orch dispatch` also writes `execution_mode` into the inbox payload and writes worktree metadata for code tasks into the attempt record and inbox payload
|
||||
- workers claim and update status through `inbox`
|
||||
- `orch reconcile` reads thread state and converts it into task state
|
||||
- `orch answer` writes an inbox `answer` message to the active thread
|
||||
@@ -459,6 +460,12 @@ Suggested success shape:
|
||||
"base_commit": "abc1234",
|
||||
"branch_name": "orch/blog_mvp_001/T4/attempt-1",
|
||||
"worktree_path": ".orch/worktrees/blog_mvp_001/T4/attempt-1"
|
||||
},
|
||||
"message": {
|
||||
"kind": "task",
|
||||
"payload_json": {
|
||||
"execution_mode": "code"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -602,6 +609,7 @@ Use this skill when you are the leader and need to control the task graph throug
|
||||
- Reconcile inbox state before making new dispatch decisions.
|
||||
- If nothing is actionable, use `orch wait` instead of manual sleep loops.
|
||||
- For code tasks, dispatch from a committed base and allocate a fresh worktree per attempt.
|
||||
- Choose `--execution-mode analysis` for read-only or review work and `--execution-mode code` for repository-writing work.
|
||||
- Keep tasks small enough to be checkable and to minimize clarification loops.
|
||||
- Use `inbox` directly only for inspection or manual repair.
|
||||
- Keep user-facing discussion in the leader.
|
||||
@@ -613,7 +621,7 @@ orch run init --run blog_mvp_001 --goal "Build blog MVP" --summary "Public blog
|
||||
orch task add --run blog_mvp_001 --task T1 --title "Project skeleton" --summary "Initialize app structure and database wiring" --default-to foundation-worker --json
|
||||
orch dep add --run blog_mvp_001 --task T2 --depends-on T1 --json
|
||||
orch ready --run blog_mvp_001 --json
|
||||
orch dispatch --run blog_mvp_001 --task T1 --to foundation-worker --base-ref main --workspace-root .orch/worktrees --strict-worktree --body-file tasks/t1.md --json
|
||||
orch dispatch --run blog_mvp_001 --task T1 --execution-mode code --to foundation-worker --base-ref main --workspace-root .orch/worktrees --body-file tasks/t1.md --json
|
||||
orch reconcile --run blog_mvp_001 --json
|
||||
orch wait --run blog_mvp_001 --for task_blocked,task_done,task_failed --after-event 0 --timeout-seconds 900 --json
|
||||
orch blocked --run blog_mvp_001 --json
|
||||
|
||||
@@ -174,14 +174,14 @@ Each case file should use this structure:
|
||||
| --- | --- | --- |
|
||||
| `leader-run-dispatch-reconcile-through-bundled-cli` | [leader-run-dispatch-reconcile-through-bundled-cli.md](./leader-run-dispatch-reconcile-through-bundled-cli.md) | validates that a leader can drive a complete `run -> task -> dispatch -> reconcile -> status` happy path through the packaged orch skill |
|
||||
| `leader-blocked-answer-resume-through-bundled-cli` | [leader-blocked-answer-resume-through-bundled-cli.md](./leader-blocked-answer-resume-through-bundled-cli.md) | validates that a leader can observe a blocked task, answer it through `orch`, and reach final completion with a real worker |
|
||||
| `strict-worktree-dispatch-to-cleanup-through-bundled-cli` | [strict-worktree-dispatch-to-cleanup-through-bundled-cli.md](./strict-worktree-dispatch-to-cleanup-through-bundled-cli.md) | validates that the skill can drive strict worktree allocation, reconcile completion, and cleanup through the bundled orch CLI |
|
||||
| `strict-worktree-dispatch-to-cleanup-through-bundled-cli` | [strict-worktree-dispatch-to-cleanup-through-bundled-cli.md](./strict-worktree-dispatch-to-cleanup-through-bundled-cli.md) | validates that the skill can drive `execution-mode code` worktree allocation, reconcile completion, and cleanup through the bundled orch CLI |
|
||||
| `leader-dispatches-dependent-task-after-prerequisite-through-bundled-cli` | [leader-dispatches-dependent-task-after-prerequisite-through-bundled-cli.md](./leader-dispatches-dependent-task-after-prerequisite-through-bundled-cli.md) | validates that a leader can use `dep add` and `ready` to hold back dependent work until a prerequisite completes, then dispatch the newly ready task |
|
||||
| `leader-cancels-active-task-through-bundled-cli` | [leader-cancels-active-task-through-bundled-cli.md](./leader-cancels-active-task-through-bundled-cli.md) | validates that a leader can cancel an already active task through the packaged orch skill without cancelling unrelated ready work |
|
||||
| `leader-answers-blocked-task-with-payload-json-through-bundled-cli` | [leader-answers-blocked-task-with-payload-json-through-bundled-cli.md](./leader-answers-blocked-task-with-payload-json-through-bundled-cli.md) | validates that a leader can answer a blocked task with structured payload data only and still drive the run to completion |
|
||||
| `leader-retries-failed-task-through-bundled-cli` | [leader-retries-failed-task-through-bundled-cli.md](./leader-retries-failed-task-through-bundled-cli.md) | validates that a leader can reconcile a failed attempt and create a successful retry through the packaged orch skill |
|
||||
| `leader-reassigns-blocked-task-through-bundled-cli` | [leader-reassigns-blocked-task-through-bundled-cli.md](./leader-reassigns-blocked-task-through-bundled-cli.md) | validates that a leader can reassign a blocked task from one worker to another and close the run through the packaged orch skill |
|
||||
| `leader-dispatches-and-launches-worker-through-codex-bridge` | [leader-dispatches-and-launches-worker-through-codex-bridge.md](./leader-dispatches-and-launches-worker-through-codex-bridge.md) | validates that a leader can dispatch a task, render a standardized worker brief, and launch a worker subagent from the same Codex thread |
|
||||
| `strict-worktree-dispatch-launches-worker-through-codex-bridge` | [strict-worktree-dispatch-launches-worker-through-codex-bridge.md](./strict-worktree-dispatch-launches-worker-through-codex-bridge.md) | validates that a leader can launch a code-writing worker subagent from saved dispatch metadata while preserving the assigned worktree contract |
|
||||
| `strict-worktree-dispatch-launches-worker-through-codex-bridge` | [strict-worktree-dispatch-launches-worker-through-codex-bridge.md](./strict-worktree-dispatch-launches-worker-through-codex-bridge.md) | validates that a leader can launch a code-writing worker subagent from saved `execution-mode code` dispatch metadata while preserving the assigned worktree contract |
|
||||
|
||||
## Scope
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_payload_answer_001, 2) add and dispatch one task T1 to worker-a, 3) wait until the task becomes blocked, 4) inspect blocked tasks, 5) answer the blocked question using payload-json only with decision=stdout, source=leader, and format=structured, 6) wait until the task completes, 7) reconcile and inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_payload_answer_001, 2) add and dispatch one task T1 to worker-a with --execution-mode analysis, 3) wait until the task becomes blocked, 4) inspect blocked tasks, 5) answer the blocked question using payload-json only with decision=stdout, source=leader, and format=structured, 6) wait until the task completes, 7) reconcile and inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
|
||||
@@ -34,7 +34,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_002, 2) add and dispatch one task T1 to worker-a, 3) wait until the task becomes blocked, 4) inspect blocked tasks, 5) answer the blocked question with the decision "Use stdout for MVP.", 6) wait until the task completes, 7) reconcile and inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_002, 2) add and dispatch one task T1 to worker-a with --execution-mode analysis, 3) wait until the task becomes blocked, 4) inspect blocked tasks, 5) answer the blocked question with the decision "Use stdout for MVP.", 6) wait until the task completes, 7) reconcile and inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
|
||||
@@ -34,7 +34,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_cancel_001, 2) add task T1 for worker-a and a second task T2 that should remain untouched, 3) dispatch T1, 4) wait until worker-a has claimed it or marked it in progress, 5) cancel T1 with a clear reason through orch, 6) inspect ready work and final run status, 7) stop after reporting THREAD_ID_1. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_cancel_001, 2) add task T1 for worker-a and a second task T2 that should remain untouched, 3) dispatch T1 with --execution-mode analysis, 4) wait until worker-a has claimed it or marked it in progress, 5) cancel T1 with a clear reason through orch, 6) inspect ready work and final run status, 7) stop after reporting THREAD_ID_1. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
|
||||
@@ -36,7 +36,7 @@ The leader is responsible for spawning the worker subagent after dispatch.
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_launch_001, 2) add exactly one task T1 assigned to worker-a, 3) dispatch it with --json saved to TMPDIR/dispatch.json, 4) render a worker brief with ORCH_SKILL_PATH/assets/orch-worker-brief into TMPDIR/worker-brief.txt, 5) spawn one worker subagent that uses INBOX_SKILL_PATH and the generated worker brief, 6) wait or poll until the worker reports completion, 7) inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker; the launched worker must use inbox only.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_launch_001, 2) add exactly one task T1 assigned to worker-a, 3) dispatch it with --execution-mode analysis and save --json to TMPDIR/dispatch.json, 4) render a worker brief with ORCH_SKILL_PATH/assets/orch-worker-brief into TMPDIR/worker-brief.txt, 5) spawn one worker subagent that uses INBOX_SKILL_PATH and the generated worker brief, 6) wait or poll until the worker reports completion, 7) inspect final status, 8) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker; the launched worker must use inbox only.
|
||||
```
|
||||
|
||||
## Execution Parameters
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_deps_001, 2) add prerequisite task T1 for worker-a and dependent task T2 for worker-b, 3) make T2 depend on T1, 4) inspect ready work and confirm only T1 is dispatchable at first, 5) dispatch T1, 6) wait until T1 completes, 7) reconcile and inspect ready work again, 8) dispatch T2 only after it becomes ready, 9) wait until T2 completes, 10) reconcile and inspect final status, 11) stop after reporting THREAD_ID_1 and THREAD_ID_2. Do not use ordinary chat to coordinate with the workers.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_deps_001, 2) add prerequisite task T1 for worker-a and dependent task T2 for worker-b, 3) make T2 depend on T1, 4) inspect ready work and confirm only T1 is dispatchable at first, 5) dispatch T1 with --execution-mode analysis, 6) wait until T1 completes, 7) reconcile and inspect ready work again, 8) dispatch T2 only after it becomes ready with --execution-mode analysis, 9) wait until T2 completes, 10) reconcile and inspect final status, 11) stop after reporting THREAD_ID_1 and THREAD_ID_2. Do not use ordinary chat to coordinate with the workers.
|
||||
```
|
||||
|
||||
### Worker A Prompt
|
||||
|
||||
@@ -35,7 +35,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_reassign_001, 2) add and dispatch one task T1 to worker-a, 3) wait until worker-a blocks, 4) inspect blocked tasks, 5) reassign T1 to worker-b with a short reason, 6) wait until worker-b completes the new attempt, 7) reconcile and inspect final status, 8) stop after reporting THREAD_ID_1 and THREAD_ID_2. Do not use ordinary chat to coordinate with the workers.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_reassign_001, 2) add and dispatch one task T1 to worker-a with --execution-mode analysis, 3) wait until worker-a blocks, 4) inspect blocked tasks, 5) reassign T1 to worker-b with a short reason, 6) wait until worker-b completes the new attempt, 7) reconcile and inspect final status, 8) stop after reporting THREAD_ID_1 and THREAD_ID_2. Do not use ordinary chat to coordinate with the workers.
|
||||
```
|
||||
|
||||
### Worker A Prompt
|
||||
|
||||
@@ -34,7 +34,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_retry_001, 2) add and dispatch one task T1 to worker-a, 3) wait until the first attempt fails, 4) reconcile, 5) retry T1 with a short retry note, 6) wait until the retried attempt completes, 7) reconcile again and inspect final status, 8) stop after reporting RUN_ID, THREAD_ID_1, and THREAD_ID_2. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_retry_001, 2) add and dispatch one task T1 to worker-a with --execution-mode analysis, 3) wait until the first attempt fails, 4) reconcile, 5) retry T1 with a short retry note, 6) wait until the retried attempt completes, 7) reconcile again and inspect final status, 8) stop after reporting RUN_ID, THREAD_ID_1, and THREAD_ID_2. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
|
||||
@@ -33,7 +33,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_001, 2) add exactly one task T1 assigned to worker-a, 3) dispatch it, 4) wait or poll until the worker reports completion, 5) reconcile the run, 6) inspect final status, 7) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_001, 2) add exactly one task T1 assigned to worker-a, 3) dispatch it with --execution-mode analysis, 4) wait or poll until the worker reports completion, 5) reconcile the run, 6) inspect final status, 7) stop after reporting RUN_ID and THREAD_ID. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ The goal is to verify that a leader using the packaged `orch` skill can dispatch
|
||||
|
||||
Validate that all of the following can be true at the same time:
|
||||
|
||||
- the leader can dispatch a code task with `--strict-worktree` through the bundled orch skill
|
||||
- the leader can dispatch a code task with `--execution-mode code` through the bundled orch skill
|
||||
- the leader can turn that dispatch JSON into a stable worker brief through `./assets/orch-worker-brief`
|
||||
- the launched worker subagent uses `skills/inbox/` and reports through inbox
|
||||
- the launched worker observes the assigned `worktree_path` and completes the attempt
|
||||
@@ -37,7 +37,7 @@ The leader is responsible for spawning the code-writing worker subagent after di
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_launch_worktree_001, 2) add one code task T1 for worker-a, 3) dispatch it with --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree while saving --json to TMPDIR/dispatch.json, 4) render a worker brief with ORCH_SKILL_PATH/assets/orch-worker-brief into TMPDIR/worker-brief.txt, 5) spawn one worker subagent that uses INBOX_SKILL_PATH and the generated worker brief, 6) wait until the worker completes, 7) inspect final status, 8) clean up attempt 1, 9) stop after reporting RUN_ID, THREAD_ID, and WORKTREE_PATH. Do not use ordinary chat to coordinate with the worker; the launched worker must use inbox only and should respect the assigned worktree.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_launch_worktree_001, 2) add one code task T1 for worker-a, 3) dispatch it with --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees while saving --json to TMPDIR/dispatch.json, 4) render a worker brief with ORCH_SKILL_PATH/assets/orch-worker-brief into TMPDIR/worker-brief.txt, 5) spawn one worker subagent that uses INBOX_SKILL_PATH and the generated worker brief, 6) wait until the worker completes, 7) inspect final status, 8) clean up attempt 1, 9) stop after reporting RUN_ID, THREAD_ID, and WORKTREE_PATH. Do not use ordinary chat to coordinate with the worker; the launched worker must use inbox only and should respect the assigned worktree.
|
||||
```
|
||||
|
||||
## Execution Parameters
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
This is a `forward-test` and a worktree-lifecycle skill validation.
|
||||
|
||||
The goal is to verify that a leader using the packaged `orch` skill can allocate a strict worktree, reconcile completion, and clean that worktree up through the bundled CLI while a worker completes the task through inbox.
|
||||
The goal is to verify that a leader using the packaged `orch` skill can allocate a code-mode worktree, reconcile completion, and clean that worktree up through the bundled CLI while a worker completes the task through inbox.
|
||||
|
||||
## Purpose
|
||||
|
||||
Validate that all of the following can be true at the same time:
|
||||
|
||||
- the leader can dispatch a code task with `--strict-worktree` through the bundled orch skill
|
||||
- the leader can dispatch a code task with `--execution-mode code` through the bundled orch skill
|
||||
- the worker can complete the resulting attempt thread through inbox
|
||||
- the leader can reconcile the finished task and clean the attempt worktree
|
||||
- the final filesystem state matches the cleanup contract
|
||||
@@ -34,7 +34,7 @@ Validate that all of the following can be true at the same time:
|
||||
### Leader Prompt
|
||||
|
||||
```text
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_worktree_001, 2) add one code task T1 for worker-a, 3) dispatch it with --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree, 4) record the returned THREAD_ID and WORKTREE_PATH, 5) wait until the worker completes, 6) reconcile, 7) clean up attempt 1, 8) stop after reporting RUN_ID, THREAD_ID, and WORKTREE_PATH. Do not use ordinary chat to coordinate with the worker.
|
||||
Use $orch at ORCH_SKILL_PATH to act as leader on the already initialized SQLite DB TMPDIR/coord.db. Only coordinate through the bundled orch CLI from the skill. Workflow: 1) create run run_blog_skill_worktree_001, 2) add one code task T1 for worker-a, 3) dispatch it with --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees, 4) record the returned THREAD_ID and WORKTREE_PATH, 5) wait until the worker completes, 6) reconcile, 7) clean up attempt 1, 8) stop after reporting RUN_ID, THREAD_ID, and WORKTREE_PATH. Do not use ordinary chat to coordinate with the worker.
|
||||
```
|
||||
|
||||
### Worker Prompt
|
||||
@@ -98,7 +98,7 @@ test ! -d WORKTREE_PATH
|
||||
- observed thread id: `thr_5743259fdccb41f9bb33dce0040b27a5`
|
||||
- observed worktree suffix: `.orch/worktrees/run-blog-skill-worktree-001/T1/attempt-1`
|
||||
- evidence summary:
|
||||
- `orch dispatch --strict-worktree` returned `base_ref == "HEAD"`, a concrete `base_commit`, branch `orch/run-blog-skill-worktree-001/T1/attempt-1`, and a non-empty `worktree_path`
|
||||
- `orch dispatch --execution-mode code` returned `base_ref == "HEAD"`, a concrete `base_commit`, branch `orch/run-blog-skill-worktree-001/T1/attempt-1`, and a non-empty `worktree_path`
|
||||
- the task payload stored on the worker thread exposed the same `worktree_path`
|
||||
- final `orch status --run run_blog_skill_worktree_001 --json` returned `run.status == "done"` and `tasks[0].status == "done"`
|
||||
- final `orch cleanup --run run_blog_skill_worktree_001 --task T1 --json` returned one cleaned attempt and the worktree directory no longer existed afterward
|
||||
@@ -114,6 +114,6 @@ test ! -d WORKTREE_PATH
|
||||
- observed thread id: `thr_089527cd07f74b52a524ba07ed74c2e4`
|
||||
- observed worktree path: `/private/tmp/orch-skill-subagents.J1XWgs/strict-worktree-dispatch-to-cleanup-through-bundled-cli/repo/.orch/worktrees/run-blog-skill-worktree-001/T1/attempt-1`
|
||||
- evidence summary:
|
||||
- a real leader agent using `skills/orch/` completed strict `dispatch`, `wait`, `reconcile`, `cleanup`, and `status`
|
||||
- a real leader agent using `skills/orch/` completed code-mode `dispatch`, `wait`, `reconcile`, `cleanup`, and `status`
|
||||
- a real worker agent using `skills/inbox/` claimed the thread and finished it with `done`
|
||||
- main-thread validation confirmed that the task payload did include the same `worktree_path` even though the worker agent summary failed to notice it, and also confirmed the worktree directory no longer existed after cleanup
|
||||
|
||||
@@ -22,7 +22,7 @@ Snapshot date:
|
||||
|
||||
Current state:
|
||||
|
||||
- `orch` CLI is implemented for the current scheduler, strict worktree, wait, and council review surfaces
|
||||
- `orch` CLI is implemented for the current scheduler, explicit execution-mode dispatch, wait, and council review surfaces
|
||||
- automated Go tests now cover every currently documented `orch` command case and workflow case, combining the original integration suite with focused contract tests for run/task/ready/dispatch/blocked/answer/cleanup/status/reconcile/workflow/council-report edges
|
||||
- `status` coverage now also documents the richer leader view: auto-reconcile plus latest attempt, latest message, and blocked-question context
|
||||
- this roadmap now exists under `docs/tests/orch/ROADMAP.md`
|
||||
@@ -244,8 +244,8 @@ docs/tests/orch/
|
||||
| `docs/tests/orch/dispatch/dispatch-creates-strict-worktree.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-rejects-dirty-repo-without-base-ref.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-allows-explicit-base-ref-on-dirty-repo.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-auto-enables-worktree-for-code-like-task.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-skips-auto-worktree-for-non-code-task.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-requires-explicit-execution-mode.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-analysis-mode-skips-worktree.md` | `dispatch` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/reconcile/README.md` | `reconcile` command case index | 0 | 0 | done |
|
||||
| `docs/tests/orch/reconcile/reconcile-maps-claimed-or-in-progress-thread-to-running.md` | `reconcile` command case | 1 | 1 | done |
|
||||
| `docs/tests/orch/reconcile/reconcile-maps-done-or-failed-thread-to-terminal-task-state.md` | `reconcile` command case | 1 | 1 | done |
|
||||
@@ -303,7 +303,7 @@ docs/tests/orch/
|
||||
| --- | --- | --- | --- |
|
||||
| `docs/tests/orch/workflows/README.md` | `run-dispatch-reconcile-status-happy-path` | end-to-end happy path from run creation through final status | done |
|
||||
| `docs/tests/orch/workflows/README.md` | `dependency-blocked-answer-resume-flow` | dependency gating plus blocked question and answer recovery | done |
|
||||
| `docs/tests/orch/workflows/README.md` | `strict-worktree-dispatch-to-cleanup` | worktree-backed code task flows from dispatch through cleanup | done |
|
||||
| `docs/tests/orch/workflows/README.md` | `code-mode-dispatch-to-cleanup` | worktree-backed code task flows from dispatch through cleanup | done |
|
||||
| `docs/tests/orch/workflows/README.md` | `council-review-end-to-end` | council workflow runs from reviewer dispatch through final report | done |
|
||||
| `docs/tests/orch/run-init/run-init-creates-new-run.md` | `run-init-creates-new-run` | creates a run with goal and optional summary | done |
|
||||
| `docs/tests/orch/run-show/run-show-returns-run-summary-and-task-counts.md` | `run-show-returns-run-summary-and-task-counts` | shows aggregate run metadata after activity | done |
|
||||
@@ -315,11 +315,11 @@ docs/tests/orch/
|
||||
| `docs/tests/orch/ready/ready-orders-by-priority-and-respects-limit.md` | `ready-orders-by-priority-and-respects-limit` | ready output orders by priority and applies explicit limit truncation | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-creates-attempt-and-thread-for-ready-task.md` | `dispatch-creates-attempt-and-thread-for-ready-task` | ready task dispatch creates attempt, thread, and task message | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-rejects-non-ready-task.md` | `dispatch-rejects-non-ready-task` | dispatch on gated task returns invalid_state | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-creates-strict-worktree.md` | `dispatch-creates-strict-worktree` | explicit strict worktree dispatch provisions isolated workspace metadata | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-rejects-dirty-repo-without-base-ref.md` | `dispatch-rejects-dirty-repo-without-base-ref` | dirty repository without explicit base ref is rejected in strict mode | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-allows-explicit-base-ref-on-dirty-repo.md` | `dispatch-allows-explicit-base-ref-on-dirty-repo` | explicit base ref allows dispatch from a dirty repository | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-auto-enables-worktree-for-code-like-task.md` | `dispatch-auto-enables-worktree-for-code-like-task` | code-like task metadata auto-enables worktree mode | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-skips-auto-worktree-for-non-code-task.md` | `dispatch-skips-auto-worktree-for-non-code-task` | clearly non-code tasks stay on normal dispatch path | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-creates-strict-worktree.md` | `dispatch-creates-strict-worktree` | explicit `execution-mode code` dispatch provisions isolated workspace metadata | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-rejects-dirty-repo-without-base-ref.md` | `dispatch-rejects-dirty-repo-without-base-ref` | dirty repository without explicit base ref is rejected in `execution-mode code` | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-allows-explicit-base-ref-on-dirty-repo.md` | `dispatch-allows-explicit-base-ref-on-dirty-repo` | explicit base ref allows `execution-mode code` dispatch from a dirty repository | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-requires-explicit-execution-mode.md` | `dispatch-requires-explicit-execution-mode` | dispatch rejects calls that omit `--execution-mode analysis|code` | done |
|
||||
| `docs/tests/orch/dispatch/dispatch-analysis-mode-skips-worktree.md` | `dispatch-analysis-mode-skips-worktree` | analysis mode stays on the normal non-worktree path | done |
|
||||
| `docs/tests/orch/reconcile/reconcile-maps-claimed-or-in-progress-thread-to-running.md` | `reconcile-maps-claimed-or-in-progress-thread-to-running` | reconcile maps active inbox execution to running task state | done |
|
||||
| `docs/tests/orch/reconcile/reconcile-maps-done-or-failed-thread-to-terminal-task-state.md` | `reconcile-maps-done-or-failed-thread-to-terminal-task-state` | reconcile maps terminal inbox states to terminal task states | done |
|
||||
| `docs/tests/orch/wait/wait-wakes-on-matching-run-event.md` | `wait-wakes-on-matching-run-event` | wait wakes on a later matching run-scoped event | done |
|
||||
|
||||
@@ -104,7 +104,7 @@ Relevant commands:
|
||||
Cases covering worktree behavior should state:
|
||||
|
||||
- whether the source repository is clean or dirty
|
||||
- whether `--strict-worktree` is enabled explicitly or inferred automatically
|
||||
- whether `--execution-mode code` is selected explicitly
|
||||
- whether `--base-ref` is omitted or explicitly provided
|
||||
- where the expected worktree path should be created
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
| --- | --- | --- |
|
||||
| `dispatch-creates-attempt-and-thread-for-ready-task` | [dispatch-creates-attempt-and-thread-for-ready-task.md](./dispatch-creates-attempt-and-thread-for-ready-task.md) | dispatches a ready task into a new attempt, inbox thread, and initial task message |
|
||||
| `dispatch-rejects-non-ready-task` | [dispatch-rejects-non-ready-task.md](./dispatch-rejects-non-ready-task.md) | rejects dispatch when the task is still gated by dependencies |
|
||||
| `dispatch-creates-strict-worktree` | [dispatch-creates-strict-worktree.md](./dispatch-creates-strict-worktree.md) | provisions a strict worktree and writes workspace metadata into the attempt and payload |
|
||||
| `dispatch-rejects-dirty-repo-without-base-ref` | [dispatch-rejects-dirty-repo-without-base-ref.md](./dispatch-rejects-dirty-repo-without-base-ref.md) | blocks strict worktree dispatch from a dirty repository without an explicit base ref |
|
||||
| `dispatch-creates-strict-worktree` | [dispatch-creates-strict-worktree.md](./dispatch-creates-strict-worktree.md) | provisions a code-mode worktree and writes workspace metadata into the attempt and payload |
|
||||
| `dispatch-rejects-dirty-repo-without-base-ref` | [dispatch-rejects-dirty-repo-without-base-ref.md](./dispatch-rejects-dirty-repo-without-base-ref.md) | blocks code-mode worktree dispatch from a dirty repository without an explicit base ref |
|
||||
| `dispatch-allows-explicit-base-ref-on-dirty-repo` | [dispatch-allows-explicit-base-ref-on-dirty-repo.md](./dispatch-allows-explicit-base-ref-on-dirty-repo.md) | accepts dirty repository state when `--base-ref` resolves to a concrete commit |
|
||||
| `dispatch-auto-enables-worktree-for-code-like-task` | [dispatch-auto-enables-worktree-for-code-like-task.md](./dispatch-auto-enables-worktree-for-code-like-task.md) | auto-enables worktree mode for code-like tasks when no explicit worktree flags are supplied |
|
||||
| `dispatch-skips-auto-worktree-for-non-code-task` | [dispatch-skips-auto-worktree-for-non-code-task.md](./dispatch-skips-auto-worktree-for-non-code-task.md) | keeps clearly non-code tasks on the normal non-worktree dispatch path |
|
||||
| `dispatch-requires-explicit-execution-mode` | [dispatch-requires-explicit-execution-mode.md](./dispatch-requires-explicit-execution-mode.md) | rejects dispatch when the caller does not declare `--execution-mode analysis|code` |
|
||||
| `dispatch-analysis-mode-skips-worktree` | [dispatch-analysis-mode-skips-worktree.md](./dispatch-analysis-mode-skips-worktree.md) | keeps analysis-mode tasks on the normal non-worktree dispatch path |
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_worktree_003 --goal "Validate explicit base ref on dirty repo"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_worktree_003 --task T1 --title "Implement backend" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_003 --task T1 --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree --base-ref HEAD
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_003 --task T1 --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees --base-ref HEAD
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Case: `dispatch-analysis-mode-skips-worktree`
|
||||
|
||||
## 用例意义
|
||||
|
||||
验证 `dispatch --execution-mode analysis` 会保持 thread-only,不会创建 worktree。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 已存在 run `run_blog_auto_worktree_002`
|
||||
- 已存在任务 `T1`
|
||||
|
||||
## 输入
|
||||
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_auto_worktree_002 --goal "Validate analysis-mode dispatch fallback"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_auto_worktree_002 --task T1 --title "Review QA findings" --summary "Summarize test failures and next steps" --default-to qa-worker
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_auto_worktree_002 --task T1 --execution-mode analysis
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
- `dispatch` 退出码为 `0`
|
||||
- `data.attempt.worktree_path == ""`
|
||||
- `data.attempt.workspace_status == ""`
|
||||
- `data.message.payload_json.execution_mode == "analysis"`
|
||||
- 仍会正常返回 `thread_id` 与首条任务消息
|
||||
|
||||
## 断言结论
|
||||
|
||||
- analysis mode 始终走标准 dispatch 路径,不会平白引入分支和工作目录
|
||||
- worker brief 和桥接层可以从 payload 中读取显式的 `execution_mode`
|
||||
@@ -1,34 +0,0 @@
|
||||
# Case: `dispatch-auto-enables-worktree-for-code-like-task`
|
||||
|
||||
## 用例意义
|
||||
|
||||
验证 `dispatch` 在未显式传 worktree flags 时,会对 code-like 任务自动启用 worktree 流程。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- `TMPDIR/repo` 是一个干净的 Git 仓库
|
||||
- 已存在 code-like 任务 `T1`
|
||||
|
||||
## 输入
|
||||
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_auto_worktree_001 --goal "Validate auto worktree detection"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_auto_worktree_001 --task T1 --title "Implement backend API" --default-to backend-worker
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_auto_worktree_001 --task T1 --repo-path TMPDIR/repo
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
- `dispatch` 退出码为 `0`
|
||||
- `data.attempt.worktree_path` 为非空
|
||||
- `data.attempt.workspace_status == "created"`
|
||||
- 返回的 worktree 路径在磁盘上存在
|
||||
|
||||
## 断言结论
|
||||
|
||||
- `dispatch` 存在自动 worktree 推断逻辑,不要求 leader 每次显式写 `--strict-worktree`
|
||||
|
||||
## 补充约束
|
||||
|
||||
- 当前推断主要依赖任务角色与 acceptance JSON 的 code-like 标记
|
||||
- 未指定 `--workspace-root` 时,自动 worktree 模式默认写到仓库下的 `.orch/worktrees`
|
||||
@@ -14,7 +14,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_001 --goal "Build blog MVP" --summary "Public blog plus admin CRUD"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_001 --task T1 --title "Implement retry policy" --summary "Add retry policy to HTTP client" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
@@ -26,6 +26,7 @@ orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "I
|
||||
- `data.attempt.assigned_to == "worker-a"`
|
||||
- `data.thread.thread_id` 与 `data.attempt.thread_id` 一致
|
||||
- `data.message.kind == "task"`
|
||||
- `data.message.payload_json.execution_mode == "analysis"`
|
||||
|
||||
## 断言结论
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 用例意义
|
||||
|
||||
验证显式 `--strict-worktree` dispatch 会创建隔离 worktree,并把 workspace 元数据持久化到 attempt 与任务 payload 中。
|
||||
验证显式 `--execution-mode code` dispatch 会创建隔离 worktree,并把 workspace 元数据持久化到 attempt 与任务 payload 中。
|
||||
|
||||
## 前置条件
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_worktree_001 --goal "Validate strict worktree dispatch"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_worktree_001 --task T1 --title "Implement backend" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_001 --task T1 --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree --body "Implement inside isolated worktree."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_001 --task T1 --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees --body "Implement inside isolated worktree."
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
@@ -26,11 +26,12 @@ orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_001 --task T1
|
||||
- `data.attempt.branch_name == "orch/run-blog-worktree-001/T1/attempt-1"`
|
||||
- 返回非空 `data.attempt.worktree_path`
|
||||
- `data.attempt.workspace_status == "created"`
|
||||
- `data.message.payload_json.execution_mode == "code"`
|
||||
- `data.message.payload_json.worktree_path` 与 attempt 中的路径一致
|
||||
|
||||
## 断言结论
|
||||
|
||||
- strict worktree dispatch 会创建真正的隔离工作目录,而不是只记录一组字符串元数据
|
||||
- code-mode dispatch 会创建真正的隔离工作目录,而不是只记录一组字符串元数据
|
||||
- worker 读取任务 payload 时可以拿到同一份 worktree 路径
|
||||
|
||||
## 补充约束
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_worktree_002 --goal "Validate dirty repo rejection"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_worktree_002 --task T1 --title "Implement backend" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_002 --task T1 --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_002 --task T1 --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
@@ -17,7 +17,7 @@ orch --db TMPDIR/coord.db --json run init --run run_blog_003 --goal "Validate re
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_003 --task T1 --title "Backend"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_003 --task T2 --title "Frontend"
|
||||
orch --db TMPDIR/coord.db --json dep add --run run_blog_003 --task T2 --depends-on T1
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_003 --task T2
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_003 --task T2 --execution-mode analysis
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Case: `dispatch-requires-explicit-execution-mode`
|
||||
|
||||
## 用例意义
|
||||
|
||||
验证 `dispatch` 不再做 worktree heuristics,而是要求 caller 显式声明 `--execution-mode analysis|code`。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 已存在 run `run_blog_auto_worktree_001`
|
||||
- 已存在任务 `T1`
|
||||
|
||||
## 输入
|
||||
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_auto_worktree_001 --goal "Validate explicit execution mode"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_auto_worktree_001 --task T1 --title "Implement backend API" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_auto_worktree_001 --task T1
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
- `dispatch` 退出码为 `30`
|
||||
- JSON 错误码为 `invalid_input`
|
||||
- 错误消息指出必须提供 `--execution-mode`
|
||||
|
||||
## 断言结论
|
||||
|
||||
- dispatch 的执行模式是显式契约,而不是由 runtime 根据任务元数据自行猜测
|
||||
- leader 必须对 thread-only 与 worktree-backed 执行路径负责
|
||||
@@ -1,35 +0,0 @@
|
||||
# Case: `dispatch-skips-auto-worktree-for-non-code-task`
|
||||
|
||||
## 用例意义
|
||||
|
||||
验证 `dispatch` 在未显式传 worktree flags 时,不会把明显非代码任务错误地推进到 worktree 执行路径。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- `TMPDIR/repo` 是一个干净的 Git 仓库
|
||||
- 已存在 run `run_blog_auto_worktree_002`
|
||||
- 已存在非 code-like 任务 `T1`
|
||||
|
||||
## 输入
|
||||
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_auto_worktree_002 --goal "Validate non-code dispatch fallback"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_auto_worktree_002 --task T1 --title "Review QA findings" --summary "Summarize test failures and next steps" --default-to qa-worker
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_auto_worktree_002 --task T1 --repo-path TMPDIR/repo
|
||||
```
|
||||
|
||||
## 预期输出
|
||||
|
||||
- `dispatch` 退出码为 `0`
|
||||
- `data.attempt.worktree_path == ""`
|
||||
- `data.attempt.workspace_status == ""`
|
||||
- 仍会正常返回 `thread_id` 与首条任务消息
|
||||
|
||||
## 断言结论
|
||||
|
||||
- 自动 worktree 推断不是“见仓库就建 worktree”
|
||||
- 非代码任务仍走标准 dispatch 路径,不会平白引入分支和工作目录
|
||||
|
||||
## 补充约束
|
||||
|
||||
- 当前非代码判断通常来自任务标题、摘要、角色和 acceptance 信息都缺少 code-like 信号
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_001 --goal "Build blog MVP"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_001 --task T1 --title "Implement retry policy" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started"
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_001
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_001 --goal "Build blog MVP"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_001 --task T1 --title "Implement retry policy" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body "The HTTP client now retries transient failures."
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_001
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_002 --goal "Build blog MVP"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_002 --task T1 --title "Implement retry policy" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status blocked --summary "Need logging decision" --payload-json '{"question":"Should retry attempts be logged?"}'
|
||||
orch --db TMPDIR/coord.db --json status --run run_blog_002
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_001 --goal "Build blog MVP"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_001 --task T1 --title "Implement retry policy" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body "The HTTP client now retries transient failures."
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_001
|
||||
|
||||
@@ -27,7 +27,7 @@ All examples assume:
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_001 --goal "Build blog MVP" --summary "Public blog plus admin CRUD"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_001 --task T1 --title "Implement retry policy" --summary "Add retry policy to HTTP client" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json ready --run run_blog_001
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --body "Implement retry handling for the HTTP client."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_001 --task T1 --execution-mode analysis --body "Implement retry handling for the HTTP client."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started"
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_001
|
||||
@@ -71,12 +71,12 @@ orch --db TMPDIR/coord.db --json task add --run run_blog_002 --task T1 --title "
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_002 --task T2 --title "Build frontend" --summary "Implement frontend flows" --default-to worker-b
|
||||
orch --db TMPDIR/coord.db --json dep add --run run_blog_002 --task T2 --depends-on T1
|
||||
orch --db TMPDIR/coord.db --json ready --run run_blog_002
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T1
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T1 --execution-mode analysis
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_BACKEND
|
||||
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_BACKEND --summary "Backend complete"
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_002
|
||||
orch --db TMPDIR/coord.db --json ready --run run_blog_002
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T2
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_002 --task T2 --execution-mode analysis
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-b --thread THREAD_FRONTEND
|
||||
inbox --db TMPDIR/coord.db --json update --agent worker-b --thread THREAD_FRONTEND --status blocked --summary "Need logging decision" --payload-json '{"question":"stdout or stderr?"}'
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_002
|
||||
@@ -101,11 +101,11 @@ orch --db TMPDIR/coord.db --json status --run run_blog_002
|
||||
- 依赖门控和 blocked-answer 机制在同一个 run 中可以顺序衔接
|
||||
- `answer` 不直接改 task 状态;真正的状态恢复仍依赖 worker 继续推进线程并由 `reconcile` 采集
|
||||
|
||||
## case: strict-worktree-dispatch-to-cleanup
|
||||
## case: code-mode-dispatch-to-cleanup
|
||||
|
||||
### 用例意义
|
||||
|
||||
验证代码任务的 strict worktree 路径能从 dispatch 一直走到 cleanup,确保隔离工作区既会被创建,也能在完成后被移除。
|
||||
验证代码任务的 `execution-mode code` worktree 路径能从 dispatch 一直走到 cleanup,确保隔离工作区既会被创建,也能在完成后被移除。
|
||||
|
||||
### 前置条件
|
||||
|
||||
@@ -115,9 +115,9 @@ orch --db TMPDIR/coord.db --json status --run run_blog_002
|
||||
### 输入
|
||||
|
||||
```bash
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_worktree_001 --goal "Validate strict worktree dispatch"
|
||||
orch --db TMPDIR/coord.db --json run init --run run_blog_worktree_001 --goal "Validate code-mode worktree dispatch"
|
||||
orch --db TMPDIR/coord.db --json task add --run run_blog_worktree_001 --task T1 --title "Implement backend" --default-to worker-a
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_001 --task T1 --repo-path TMPDIR/repo --workspace-root .orch/worktrees --strict-worktree --body "Implement inside isolated worktree."
|
||||
orch --db TMPDIR/coord.db --json dispatch --run run_blog_worktree_001 --task T1 --execution-mode code --repo-path TMPDIR/repo --workspace-root .orch/worktrees --body "Implement inside isolated worktree."
|
||||
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID
|
||||
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Backend complete"
|
||||
orch --db TMPDIR/coord.db --json reconcile --run run_blog_worktree_001
|
||||
@@ -133,7 +133,7 @@ orch --db TMPDIR/coord.db --json cleanup --run run_blog_worktree_001 --task T1 -
|
||||
|
||||
### 断言结论
|
||||
|
||||
- strict worktree 不是单次 dispatch 细节,而是完整 attempt 生命周期的一部分
|
||||
- `execution-mode code` 的 worktree 不是单次 dispatch 细节,而是完整 attempt 生命周期的一部分
|
||||
- `cleanup` 的目标是已完成或废弃的工作区,不应误删仍在活动中的执行目录
|
||||
|
||||
## case: council-review-end-to-end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines how code-writing workers should execute in isolated Git worktrees instead of modifying the user's primary working tree.
|
||||
This document defines how `orch dispatch --execution-mode code` should run code-writing workers in isolated Git worktrees instead of modifying the user's primary working tree.
|
||||
|
||||
The default recommendation is strict mode:
|
||||
|
||||
@@ -11,7 +11,7 @@ The default recommendation is strict mode:
|
||||
- the worker writes code only inside that assigned worktree
|
||||
- the leader reviews and integrates the result later
|
||||
|
||||
This model is intended for code tasks. Non-code tasks may not need a worktree.
|
||||
This model is intended for `execution-mode code`. `execution-mode analysis` stays thread-only and does not allocate a worktree.
|
||||
|
||||
## Why Worktrees
|
||||
|
||||
@@ -25,11 +25,11 @@ Using a worktree per task attempt gives:
|
||||
|
||||
## Strict Mode
|
||||
|
||||
Strict mode means workers only start from an explicit committed Git base.
|
||||
Code mode means workers only start from an explicit committed Git base.
|
||||
|
||||
Rules:
|
||||
|
||||
- `orch dispatch` must resolve a concrete `base_ref` to a commit
|
||||
- `orch dispatch --execution-mode code` must resolve a concrete `base_ref` to a commit
|
||||
- worker execution must happen in a separate worktree
|
||||
- the worker must not modify the user's primary checkout
|
||||
- each retry gets a fresh attempt and a fresh worktree
|
||||
@@ -54,9 +54,9 @@ This keeps worker execution reproducible and avoids hidden divergence from the l
|
||||
|
||||
### 1. Leader Decides a Task Is Ready
|
||||
|
||||
`orch` determines that a task may be dispatched.
|
||||
`orch` determines that a task may be dispatched, and the leader chooses `--execution-mode code`.
|
||||
|
||||
### 2. `orch dispatch` Creates an Attempt Workspace
|
||||
### 2. `orch dispatch --execution-mode code` Creates an Attempt Workspace
|
||||
|
||||
For one task attempt, `orch` should:
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ type WaitResult struct {
|
||||
}
|
||||
|
||||
type DispatchWorkspace struct {
|
||||
ExecutionMode string `json:"execution_mode,omitempty"`
|
||||
BaseRef string `json:"base_ref,omitempty"`
|
||||
BaseCommit string `json:"base_commit,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"`
|
||||
@@ -1094,7 +1095,8 @@ func (s *OrchStore) dispatchTaskTx(
|
||||
|
||||
attemptNo := task.LatestAttemptNo + 1
|
||||
workspace := DispatchWorkspace{
|
||||
BaseRef: strings.TrimSpace(baseRef),
|
||||
ExecutionMode: "analysis",
|
||||
BaseRef: strings.TrimSpace(baseRef),
|
||||
}
|
||||
finalizeWorkspace := func(success bool) {}
|
||||
if prepareWorkspace != nil {
|
||||
@@ -2636,6 +2638,9 @@ func buildDispatchPayload(task Task, attemptNo int, workspace DispatchWorkspace)
|
||||
payload["acceptance"] = acceptance
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(workspace.ExecutionMode) != "" {
|
||||
payload["execution_mode"] = strings.TrimSpace(workspace.ExecutionMode)
|
||||
}
|
||||
if strings.TrimSpace(workspace.BaseRef) != "" {
|
||||
payload["base_ref"] = strings.TrimSpace(workspace.BaseRef)
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ func seedBlockedTaskForAnswerCleanupEdgeTests(t *testing.T, dbPath, runID, taskI
|
||||
"dispatch",
|
||||
"--run", runID,
|
||||
"--task", taskID,
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
|
||||
@@ -89,6 +89,7 @@ func TestOrchDispatchCreatesAttemptAndThreadForReadyTask(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_dispatch_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
"--body", "Implement retry handling for the HTTP client.",
|
||||
)
|
||||
|
||||
@@ -167,6 +168,7 @@ func TestOrchBlockedListsLatestQuestionForBlockedTask(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_blocked_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var firstDispatchResp map[string]any
|
||||
@@ -205,6 +207,7 @@ func TestOrchBlockedListsLatestQuestionForBlockedTask(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_blocked_001",
|
||||
"--task", "T2",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var secondDispatchResp map[string]any
|
||||
@@ -305,6 +308,7 @@ func TestOrchStatusReturnsRunSummaryAndTaskList(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_status_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
"--body", "Implement retry handling for the HTTP client.",
|
||||
)
|
||||
|
||||
@@ -420,6 +424,7 @@ func TestOrchStatusAutoReconcilesAndIncludesBlockedContext(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_status_002",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
"--body", "Implement retry handling for the HTTP client.",
|
||||
)
|
||||
|
||||
@@ -522,6 +527,7 @@ func TestOrchReconcileMapsFailedThreadToTerminalTaskState(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_reconcile_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
"--body", "Implement retry handling for the HTTP client.",
|
||||
)
|
||||
|
||||
@@ -595,7 +601,7 @@ func TestOrchReconcileMapsFailedThreadToTerminalTaskState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchWorkflowStrictWorktreeDispatchToCleanup(t *testing.T) {
|
||||
func TestOrchWorkflowCodeModeDispatchToCleanup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
@@ -627,9 +633,9 @@ func TestOrchWorkflowStrictWorktreeDispatchToCleanup(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_workflow_worktree_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
"--body", "Implement inside isolated worktree.",
|
||||
)
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
)
|
||||
|
||||
type dispatchOptions struct {
|
||||
runID string
|
||||
taskID string
|
||||
toAgent string
|
||||
body string
|
||||
bodyFile string
|
||||
baseRef string
|
||||
repoPath string
|
||||
workspaceRoot string
|
||||
strictWorktree bool
|
||||
runID string
|
||||
taskID string
|
||||
toAgent string
|
||||
body string
|
||||
bodyFile string
|
||||
executionMode string
|
||||
baseRef string
|
||||
repoPath string
|
||||
workspaceRoot string
|
||||
}
|
||||
|
||||
func newDispatchCmd(root *rootOptions) *cobra.Command {
|
||||
@@ -28,6 +28,11 @@ func newDispatchCmd(root *rootOptions) *cobra.Command {
|
||||
Use: "dispatch",
|
||||
Short: "Dispatch a ready task to a worker through inbox",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
normalizedOpts, err := normalizeDispatchOptions(*opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := resolveBodyValue(opts.body, opts.bodyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,12 +46,12 @@ func newDispatchCmd(root *rootOptions) *cobra.Command {
|
||||
defer sqlDB.Close()
|
||||
|
||||
result, err := store.NewOrchStore(sqlDB).DispatchTask(ctx, store.DispatchInput{
|
||||
RunID: opts.runID,
|
||||
TaskID: opts.taskID,
|
||||
ToAgent: opts.toAgent,
|
||||
RunID: normalizedOpts.runID,
|
||||
TaskID: normalizedOpts.taskID,
|
||||
ToAgent: normalizedOpts.toAgent,
|
||||
Body: body,
|
||||
BaseRef: opts.baseRef,
|
||||
PrepareWorkspace: newDispatchWorkspacePreparer(cmd, *opts),
|
||||
BaseRef: normalizedOpts.baseRef,
|
||||
PrepareWorkspace: newDispatchWorkspacePreparer(cmd, normalizedOpts),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -82,12 +87,13 @@ func newDispatchCmd(root *rootOptions) *cobra.Command {
|
||||
cmd.Flags().StringVar(&opts.toAgent, "to", "", "Worker agent override")
|
||||
cmd.Flags().StringVar(&opts.body, "body", "", "Task message body")
|
||||
cmd.Flags().StringVar(&opts.bodyFile, "body-file", "", "Read task message body from file")
|
||||
cmd.Flags().StringVar(&opts.executionMode, "execution-mode", "", "Execution mode: analysis or code")
|
||||
cmd.Flags().StringVar(&opts.baseRef, "base-ref", "", "Optional base ref to record on the attempt")
|
||||
cmd.Flags().StringVar(&opts.repoPath, "repo-path", "", "Source repository path for worktree dispatch")
|
||||
cmd.Flags().StringVar(&opts.workspaceRoot, "workspace-root", "", "Workspace root for worktree dispatch")
|
||||
cmd.Flags().BoolVar(&opts.strictWorktree, "strict-worktree", false, "Require strict worktree setup")
|
||||
_ = cmd.MarkFlagRequired("run")
|
||||
_ = cmd.MarkFlagRequired("task")
|
||||
_ = cmd.MarkFlagRequired("execution-mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ func TestOrchRunDispatchReconcileLifecycle(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
"--body", "Implement retry handling for the HTTP client.",
|
||||
)
|
||||
|
||||
@@ -256,6 +257,7 @@ func TestOrchDependencyBlockedAndAnswerFlow(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_002",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchBackendResp map[string]any
|
||||
@@ -316,6 +318,7 @@ func TestOrchDependencyBlockedAndAnswerFlow(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_002",
|
||||
"--task", "T2",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchFrontendResp map[string]any
|
||||
@@ -502,6 +505,7 @@ func TestOrchDispatchRejectsNonReadyTask(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_003",
|
||||
"--task", "T2",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
if exitCode != 30 {
|
||||
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
|
||||
@@ -509,7 +513,7 @@ func TestOrchDispatchRejectsNonReadyTask(t *testing.T) {
|
||||
assertErrorJSON(t, stdout, "invalid_state")
|
||||
}
|
||||
|
||||
func TestOrchDispatchCreatesStrictWorktree(t *testing.T) {
|
||||
func TestOrchDispatchCodeModeCreatesWorktree(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
@@ -541,9 +545,9 @@ func TestOrchDispatchCreatesStrictWorktree(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_worktree_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
"--body", "Implement inside isolated worktree.",
|
||||
)
|
||||
|
||||
@@ -628,7 +632,7 @@ func TestOrchDispatchCreatesStrictWorktree(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchStrictWorktreeRejectsDirtyRepoWithoutBaseRef(t *testing.T) {
|
||||
func TestOrchDispatchCodeModeRejectsDirtyRepoWithoutBaseRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
@@ -663,9 +667,9 @@ func TestOrchStrictWorktreeRejectsDirtyRepoWithoutBaseRef(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_worktree_002",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
if exitCode != 30 {
|
||||
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
|
||||
@@ -677,7 +681,7 @@ func TestOrchStrictWorktreeRejectsDirtyRepoWithoutBaseRef(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchStrictWorktreeAllowsExplicitBaseRefOnDirtyRepo(t *testing.T) {
|
||||
func TestOrchDispatchCodeModeAllowsExplicitBaseRefOnDirtyRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
@@ -714,9 +718,9 @@ func TestOrchStrictWorktreeAllowsExplicitBaseRefOnDirtyRepo(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_worktree_003",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
"--base-ref", "HEAD",
|
||||
)
|
||||
|
||||
@@ -730,9 +734,8 @@ func TestOrchStrictWorktreeAllowsExplicitBaseRefOnDirtyRepo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchDispatchAutoEnablesWorktreeForCodeLikeTask(t *testing.T) {
|
||||
func TestOrchDispatchRequiresExplicitExecutionMode(t *testing.T) {
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
repoPath := initGitRepo(t)
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
@@ -750,40 +753,26 @@ func TestOrchDispatchAutoEnablesWorktreeForCodeLikeTask(t *testing.T) {
|
||||
"--run", "run_blog_auto_worktree_001",
|
||||
"--task", "T1",
|
||||
"--title", "Implement backend API",
|
||||
"--default-to", "backend-worker",
|
||||
"--default-to", "worker-a",
|
||||
)
|
||||
|
||||
dispatchOut := runOrchCommand(
|
||||
t,
|
||||
stdout, _, exitCode := executeOrchCommand(
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"dispatch",
|
||||
"--run", "run_blog_auto_worktree_001",
|
||||
"--task", "T1",
|
||||
"--repo-path", repoPath,
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
attempt := nestedValue(t, dispatchResp, "data", "attempt").(map[string]any)
|
||||
|
||||
worktreePath, _ := attempt["worktree_path"].(string)
|
||||
if worktreePath == "" {
|
||||
t.Fatalf("expected auto-detected code task to allocate a worktree, got %#v", attempt)
|
||||
}
|
||||
if got, _ := attempt["workspace_status"].(string); got != "created" {
|
||||
t.Fatalf("expected created workspace status, got %#v", attempt["workspace_status"])
|
||||
}
|
||||
if _, err := os.Stat(worktreePath); err != nil {
|
||||
t.Fatalf("stat auto worktree path %s: %v", worktreePath, err)
|
||||
if exitCode != 30 {
|
||||
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
|
||||
}
|
||||
assertErrorJSON(t, stdout, "invalid_input")
|
||||
}
|
||||
|
||||
func TestOrchDispatchDoesNotAutoEnableWorktreeForNonCodeTask(t *testing.T) {
|
||||
func TestOrchDispatchAnalysisModeSkipsWorktree(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
repoPath := initGitRepo(t)
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
@@ -812,17 +801,23 @@ func TestOrchDispatchDoesNotAutoEnableWorktreeForNonCodeTask(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_auto_worktree_002",
|
||||
"--task", "T1",
|
||||
"--repo-path", repoPath,
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
attempt := nestedValue(t, dispatchResp, "data", "attempt").(map[string]any)
|
||||
if got, _ := attempt["worktree_path"].(string); got != "" {
|
||||
t.Fatalf("expected non-code task to stay on non-worktree path, got %#v", attempt["worktree_path"])
|
||||
t.Fatalf("expected analysis task to stay on non-worktree path, got %#v", attempt["worktree_path"])
|
||||
}
|
||||
if got, _ := attempt["workspace_status"].(string); got != "" {
|
||||
t.Fatalf("expected no workspace status for non-code task, got %#v", attempt["workspace_status"])
|
||||
t.Fatalf("expected no workspace status for analysis task, got %#v", attempt["workspace_status"])
|
||||
}
|
||||
|
||||
message := nestedValue(t, dispatchResp, "data", "message").(map[string]any)
|
||||
payload := message["payload_json"].(map[string]any)
|
||||
if got, _ := payload["execution_mode"].(string); got != "analysis" {
|
||||
t.Fatalf("expected analysis execution mode in payload, got %#v", payload["execution_mode"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,6 +852,7 @@ func TestOrchWaitWakesOnBlockedEvent(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_wait_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
@@ -877,7 +873,7 @@ func TestOrchWaitWakesOnBlockedEvent(t *testing.T) {
|
||||
"--run", "run_blog_wait_001",
|
||||
"--for", "task_blocked",
|
||||
"--after-event", "0",
|
||||
"--timeout-seconds", "2",
|
||||
"--timeout-seconds", "5",
|
||||
)
|
||||
resultCh <- waitResult{stdout: stdout, stderr: stderr, exitCode: exitCode}
|
||||
}()
|
||||
@@ -1010,9 +1006,9 @@ func TestOrchRetryCreatesNewAttempt(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_retry_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
@@ -1108,9 +1104,9 @@ func TestOrchReassignCancelsOldThreadAndDispatchesNewAttempt(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_reassign_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
@@ -1224,6 +1220,7 @@ func TestOrchCancelTaskAndRun(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "analysis",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
@@ -1344,9 +1341,9 @@ func TestOrchCleanupRemovesCompletedWorktree(t *testing.T) {
|
||||
"dispatch",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
"--task", "T1",
|
||||
"--execution-mode", "code",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
|
||||
@@ -2,7 +2,6 @@ package orch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -15,21 +14,25 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
executionModeAnalysis = "analysis"
|
||||
executionModeCode = "code"
|
||||
)
|
||||
|
||||
func newDispatchWorkspacePreparer(cmd *cobra.Command, opts dispatchOptions) store.DispatchWorkspacePreparer {
|
||||
ctx := cmd.Context()
|
||||
|
||||
return func(task store.Task, attemptNo int) (store.DispatchWorkspace, func(), error) {
|
||||
effectiveOpts, useWorktree := resolveDispatchWorktreeOptions(task, opts)
|
||||
if !useWorktree {
|
||||
return store.DispatchWorkspace{}, func() {}, nil
|
||||
if opts.executionMode == executionModeAnalysis {
|
||||
return store.DispatchWorkspace{ExecutionMode: executionModeAnalysis}, func() {}, nil
|
||||
}
|
||||
return provisionDispatchWorkspace(ctx, effectiveOpts, task, attemptNo)
|
||||
return provisionDispatchWorkspace(ctx, opts, task, attemptNo)
|
||||
}
|
||||
}
|
||||
|
||||
func newAttemptReuseWorkspacePreparer(cmd *cobra.Command, task store.Task, attempt *store.TaskAttempt) store.DispatchWorkspacePreparer {
|
||||
if attempt == nil || attempt.WorktreePath == "" {
|
||||
return nil
|
||||
return newDispatchWorkspacePreparer(cmd, dispatchOptions{executionMode: executionModeAnalysis})
|
||||
}
|
||||
|
||||
workspaceRoot, ok := deriveWorkspaceRootFromAttempt(task.RunID, task.TaskID, attempt.WorktreePath)
|
||||
@@ -43,114 +46,49 @@ func newAttemptReuseWorkspacePreparer(cmd *cobra.Command, task store.Task, attem
|
||||
}
|
||||
|
||||
opts := dispatchOptions{
|
||||
repoPath: attempt.WorktreePath,
|
||||
workspaceRoot: workspaceRoot,
|
||||
strictWorktree: true,
|
||||
baseRef: baseRef,
|
||||
executionMode: executionModeCode,
|
||||
repoPath: attempt.WorktreePath,
|
||||
workspaceRoot: workspaceRoot,
|
||||
baseRef: baseRef,
|
||||
}
|
||||
|
||||
return newDispatchWorkspacePreparer(cmd, opts)
|
||||
}
|
||||
|
||||
func dispatchUsesWorktree(opts dispatchOptions) bool {
|
||||
return strings.TrimSpace(opts.workspaceRoot) != "" ||
|
||||
opts.strictWorktree
|
||||
}
|
||||
|
||||
func resolveDispatchWorktreeOptions(task store.Task, opts dispatchOptions) (dispatchOptions, bool) {
|
||||
if dispatchUsesWorktree(opts) {
|
||||
return opts, true
|
||||
}
|
||||
if !taskLooksLikeCodeWork(task) {
|
||||
return opts, false
|
||||
func normalizeDispatchOptions(opts dispatchOptions) (dispatchOptions, error) {
|
||||
mode, err := normalizeExecutionMode(opts.executionMode)
|
||||
if err != nil {
|
||||
return dispatchOptions{}, err
|
||||
}
|
||||
|
||||
auto := opts
|
||||
auto.strictWorktree = true
|
||||
return auto, true
|
||||
}
|
||||
normalized := opts
|
||||
normalized.executionMode = mode
|
||||
|
||||
func taskLooksLikeCodeWork(task store.Task) bool {
|
||||
if acceptanceJSONLooksCodeLike(task.AcceptanceJSON) {
|
||||
return true
|
||||
}
|
||||
return roleLooksCodeLike(task.DefaultTo)
|
||||
}
|
||||
|
||||
func acceptanceJSONLooksCodeLike(raw json.RawMessage) bool {
|
||||
if len(raw) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var value any
|
||||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return false
|
||||
}
|
||||
return acceptanceValueLooksCodeLike(value)
|
||||
}
|
||||
|
||||
func acceptanceValueLooksCodeLike(value any) bool {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
for key, raw := range typed {
|
||||
lowerKey := strings.ToLower(strings.TrimSpace(key))
|
||||
switch lowerKey {
|
||||
case "code", "code_task", "writes_code", "worktree":
|
||||
if boolValue, ok := raw.(bool); ok && boolValue {
|
||||
return true
|
||||
}
|
||||
case "kind", "task_type", "mode", "type":
|
||||
if stringValue, ok := raw.(string); ok && isCodeLikeMarker(stringValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if acceptanceValueLooksCodeLike(raw) {
|
||||
return true
|
||||
}
|
||||
if mode == executionModeAnalysis {
|
||||
if strings.TrimSpace(opts.repoPath) != "" {
|
||||
return dispatchOptions{}, protocol.InvalidInput("repo-path is only valid with --execution-mode code", nil)
|
||||
}
|
||||
case []any:
|
||||
for _, item := range typed {
|
||||
if acceptanceValueLooksCodeLike(item) {
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(opts.workspaceRoot) != "" {
|
||||
return dispatchOptions{}, protocol.InvalidInput("workspace-root is only valid with --execution-mode code", nil)
|
||||
}
|
||||
case string:
|
||||
return isCodeLikeMarker(typed)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func roleLooksCodeLike(role string) bool {
|
||||
role = strings.ToLower(strings.TrimSpace(role))
|
||||
if role == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, token := range splitIdentifierTokens(role) {
|
||||
switch token {
|
||||
case "backend", "frontend", "front", "admin", "ui", "fullstack", "foundation", "db", "database", "mobile", "ios", "android", "web", "platform", "infra", "api":
|
||||
return true
|
||||
if strings.TrimSpace(opts.baseRef) != "" {
|
||||
return dispatchOptions{}, protocol.InvalidInput("base-ref is only valid with --execution-mode code", nil)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func isCodeLikeMarker(value string) bool {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
switch value {
|
||||
case "code", "code_task", "code-task", "code-change", "code_change", "implementation", "patch", "diff", "repo":
|
||||
return true
|
||||
func normalizeExecutionMode(value string) (string, error) {
|
||||
mode := strings.ToLower(strings.TrimSpace(value))
|
||||
switch mode {
|
||||
case executionModeAnalysis, executionModeCode:
|
||||
return mode, nil
|
||||
default:
|
||||
return false
|
||||
return "", protocol.InvalidInput("execution-mode must be one of: analysis, code", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func splitIdentifierTokens(value string) []string {
|
||||
return strings.FieldsFunc(value, func(r rune) bool {
|
||||
return !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'))
|
||||
})
|
||||
}
|
||||
|
||||
func provisionDispatchWorkspace(ctx context.Context, opts dispatchOptions, task store.Task, attemptNo int) (store.DispatchWorkspace, func(), error) {
|
||||
repoRoot, err := resolveRepoRoot(ctx, opts.repoPath)
|
||||
if err != nil {
|
||||
@@ -162,7 +100,7 @@ func provisionDispatchWorkspace(ctx context.Context, opts dispatchOptions, task
|
||||
return store.DispatchWorkspace{}, nil, err
|
||||
}
|
||||
|
||||
baseRef, baseCommit, err := resolveDispatchBase(ctx, repoRoot, workspaceRoot, opts.baseRef, opts.strictWorktree)
|
||||
baseRef, baseCommit, err := resolveDispatchBase(ctx, repoRoot, workspaceRoot, opts.baseRef, true)
|
||||
if err != nil {
|
||||
return store.DispatchWorkspace{}, nil, err
|
||||
}
|
||||
@@ -190,6 +128,7 @@ func provisionDispatchWorkspace(ctx context.Context, opts dispatchOptions, task
|
||||
}
|
||||
|
||||
return store.DispatchWorkspace{
|
||||
ExecutionMode: executionModeCode,
|
||||
BaseRef: baseRef,
|
||||
BaseCommit: baseCommit,
|
||||
BranchName: branchName,
|
||||
|
||||
@@ -175,7 +175,7 @@ run_case_happy_path() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Implement retry handling for the HTTP client."
|
||||
|
||||
local thread_id
|
||||
@@ -257,7 +257,7 @@ run_case_blocked_answer() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Implement the worker flow and stop if a logging decision is needed."
|
||||
|
||||
local thread_id
|
||||
@@ -370,7 +370,7 @@ run_case_strict_worktree_cleanup() {
|
||||
|
||||
run_json "${case_dir}/run.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json run init \
|
||||
--run "${run_id}" --goal "Validate strict worktree dispatch" \
|
||||
--run "${run_id}" --goal "Validate code-mode worktree dispatch" \
|
||||
--summary "Exercise worktree allocation and cleanup"
|
||||
|
||||
run_json "${case_dir}/task.json" \
|
||||
@@ -381,7 +381,7 @@ run_case_strict_worktree_cleanup() {
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--repo-path "${repo_path}" --workspace-root .orch/worktrees --strict-worktree \
|
||||
--execution-mode code --repo-path "${repo_path}" --workspace-root .orch/worktrees \
|
||||
--body "Implement inside isolated worktree."
|
||||
|
||||
local thread_id
|
||||
@@ -445,7 +445,7 @@ run_case_strict_worktree_cleanup() {
|
||||
"${case_dir}" "${case_slug}" "${db_path}" "${run_id}" pass "${duration_seconds}" \
|
||||
"$(join_json_array "${thread_id}")" \
|
||||
"$(join_json_array "${worktree_path}")" \
|
||||
"Direct CLI replay of strict worktree dispatch, completion, and cleanup."
|
||||
"Direct CLI replay of execution-mode code dispatch, completion, and cleanup."
|
||||
}
|
||||
|
||||
run_case_dependency_ready_dispatch() {
|
||||
@@ -483,7 +483,7 @@ run_case_dependency_ready_dispatch() {
|
||||
|
||||
run_json "${case_dir}/dispatch-1.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Complete the prerequisite task first."
|
||||
|
||||
local thread_id_1
|
||||
@@ -522,7 +522,7 @@ run_case_dependency_ready_dispatch() {
|
||||
|
||||
run_json "${case_dir}/dispatch-2.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T2 --to worker-b \
|
||||
--run "${run_id}" --task T2 --execution-mode analysis --to worker-b \
|
||||
--body "Dependent task is now ready after T1."
|
||||
|
||||
local thread_id_2
|
||||
@@ -600,7 +600,7 @@ run_case_cancel_active_task() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Start work so the leader can cancel an active task."
|
||||
|
||||
local thread_id
|
||||
@@ -668,7 +668,7 @@ run_case_payload_only_answer() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Pause if a structured logging decision is needed."
|
||||
|
||||
local thread_id
|
||||
@@ -785,7 +785,7 @@ run_case_retry() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Initial attempt expected to fail for retry validation."
|
||||
|
||||
local thread_id_1
|
||||
@@ -899,7 +899,7 @@ run_case_reassign() {
|
||||
|
||||
run_json "${case_dir}/dispatch.json" \
|
||||
"${ORCH_BIN}" --db "${db_path}" --json dispatch \
|
||||
--run "${run_id}" --task T1 --to worker-a \
|
||||
--run "${run_id}" --task T1 --execution-mode analysis --to worker-a \
|
||||
--body "Initial attempt expected to be reassigned."
|
||||
|
||||
local thread_id_1
|
||||
|
||||
@@ -40,9 +40,9 @@ Use the bundled `./assets/orch` CLI to control leader-side orchestration through
|
||||
|
||||
- Prefer `orch` over hand-written `inbox send` for normal leader operations.
|
||||
- Treat `dispatch` as handoff, not execution. After dispatch, arrange a separate worker runtime or worker agent to claim the mapped inbox thread.
|
||||
- For analysis, review, or other read-only tasks, omit worktree flags so dispatch stays thread-only and light.
|
||||
- For analysis, review, or other read-only tasks, dispatch with `--execution-mode analysis`.
|
||||
- If nothing is actionable, use `wait` instead of manual sleep loops.
|
||||
- For code tasks, dispatch from a committed base and allocate a fresh worktree per attempt.
|
||||
- For code tasks, dispatch with `--execution-mode code` from a committed base and allocate a fresh worktree per attempt.
|
||||
- Use `blocked` and `answer` to resolve worker questions through the active attempt thread.
|
||||
- Use `retry` or `reassign` only after checking the latest task and attempt state.
|
||||
- Use `inbox` directly only for inspection or manual repair, not routine scheduling.
|
||||
@@ -64,7 +64,7 @@ Use this when the leader wants durable `orch` state plus a worker sub-agent laun
|
||||
1. save the dispatch output:
|
||||
|
||||
```bash
|
||||
./assets/orch --db ./coord.db --json dispatch --run RUN_ID --task TASK_ID > TMPDIR/dispatch.json
|
||||
./assets/orch --db ./coord.db --json dispatch --run RUN_ID --task TASK_ID --execution-mode analysis > TMPDIR/dispatch.json
|
||||
```
|
||||
|
||||
2. render a standardized worker brief:
|
||||
@@ -92,7 +92,7 @@ See:
|
||||
- The worker side should use `skills/inbox/`, not this skill.
|
||||
- The leader should pass or preserve the `dispatch` result, especially `attempt.thread_id`, `attempt.assigned_to`, and worktree metadata when present.
|
||||
- Code-writing workers should execute inside the assigned worktree path from the task payload or attempt metadata.
|
||||
- Read-only or analysis workers can stay on the normal thread-only path with no worktree.
|
||||
- Read-only or analysis workers should be dispatched with `--execution-mode analysis` and stay on the normal thread-only path with no worktree.
|
||||
|
||||
## Typical Commands
|
||||
|
||||
@@ -103,9 +103,9 @@ See:
|
||||
./assets/orch --db ./coord.db --json task add --run blog_mvp_001 --task T2 --title "Summarize flaky tests" --summary "Read logs and report next steps" --default-to qa-worker --acceptance-json '{"kind":"analysis"}'
|
||||
./assets/orch --db ./coord.db --json dep add --run blog_mvp_001 --task T2 --depends-on T1
|
||||
./assets/orch --db ./coord.db --json ready --run blog_mvp_001
|
||||
./assets/orch --db ./coord.db --json dispatch --run blog_mvp_001 --task T1 --to foundation-worker --base-ref main --workspace-root .orch/worktrees --strict-worktree --body-file tasks/t1.md > /tmp/t1-dispatch.json
|
||||
./assets/orch --db ./coord.db --json dispatch --run blog_mvp_001 --task T1 --execution-mode code --to foundation-worker --base-ref main --workspace-root .orch/worktrees --body-file tasks/t1.md > /tmp/t1-dispatch.json
|
||||
./assets/orch-worker-brief --dispatch-json /tmp/t1-dispatch.json --db ./coord.db > /tmp/t1-worker-brief.txt
|
||||
./assets/orch --db ./coord.db --json dispatch --run blog_mvp_001 --task T2 --to qa-worker --body "Read the failing test logs and summarize the root cause." > /tmp/t2-dispatch.json
|
||||
./assets/orch --db ./coord.db --json dispatch --run blog_mvp_001 --task T2 --execution-mode analysis --to qa-worker --body "Read the failing test logs and summarize the root cause." > /tmp/t2-dispatch.json
|
||||
./assets/orch-worker-brief --dispatch-json /tmp/t2-dispatch.json --db ./coord.db > /tmp/t2-worker-brief.txt
|
||||
./assets/orch --db ./coord.db --json status --run blog_mvp_001
|
||||
./assets/orch --db ./coord.db --json wait --run blog_mvp_001 --for task_blocked,task_done,task_failed --after-event 0 --timeout-seconds 900
|
||||
@@ -135,8 +135,8 @@ See:
|
||||
|
||||
## Notes
|
||||
|
||||
- `dispatch` supports `--repo-path`, `--workspace-root`, `--strict-worktree`, and `--base-ref` for worktree-backed code execution.
|
||||
- When worktree flags are omitted, code-like task metadata can still auto-enable strict worktree mode. Non-code tasks stay on the normal thread-only path.
|
||||
- `dispatch` requires `--execution-mode analysis|code`.
|
||||
- `dispatch` supports `--repo-path`, `--workspace-root`, and `--base-ref` for `--execution-mode code`.
|
||||
- `./assets/orch-worker-brief` is the supported way to turn a saved dispatch JSON response into a stable worker prompt for a spawned sub-agent.
|
||||
- `answer` supports `--payload-json` for structured decisions, not just freeform text.
|
||||
- `status` is the full run view; `run show` is the lighter aggregate view.
|
||||
|
||||
Binary file not shown.
@@ -90,6 +90,7 @@ attempt_no="$(jq -r '.data.attempt.attempt_no // empty' "${dispatch_json}")"
|
||||
assigned_to="$(jq -r '.data.attempt.assigned_to // .data.task.default_to // empty' "${dispatch_json}")"
|
||||
thread_id="$(jq -r '.data.attempt.thread_id // .data.thread.thread_id // empty' "${dispatch_json}")"
|
||||
worktree_path="$(jq -r '.data.attempt.worktree_path // empty' "${dispatch_json}")"
|
||||
execution_mode="$(jq -r '.data.message.payload_json.execution_mode // empty' "${dispatch_json}")"
|
||||
base_ref="$(jq -r '.data.attempt.base_ref // empty' "${dispatch_json}")"
|
||||
task_title="$(jq -r '.data.task.title // empty' "${dispatch_json}")"
|
||||
task_summary="$(jq -r '.data.task.summary // empty' "${dispatch_json}")"
|
||||
@@ -100,9 +101,17 @@ if [ -z "${run_id}" ] || [ -z "${task_id}" ] || [ -z "${assigned_to}" ] || [ -z
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mode="analysis"
|
||||
if [ -n "${worktree_path}" ]; then
|
||||
mode="code"
|
||||
if [ "${execution_mode}" != "analysis" ] && [ "${execution_mode}" != "code" ]; then
|
||||
printf 'dispatch json is missing a supported execution_mode in message.payload_json\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "${execution_mode}" = "code" ] && [ -z "${worktree_path}" ]; then
|
||||
printf 'dispatch json is inconsistent: execution_mode=code requires a worktree_path\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "${execution_mode}" = "analysis" ] && [ -n "${worktree_path}" ]; then
|
||||
printf 'dispatch json is inconsistent: execution_mode=analysis must not include a worktree_path\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
@@ -114,7 +123,7 @@ You are assigned one existing attempt:
|
||||
- attempt_no: ${attempt_no}
|
||||
- assigned_to: ${assigned_to}
|
||||
- thread_id: ${thread_id}
|
||||
- execution_mode: ${mode}
|
||||
- execution_mode: ${execution_mode}
|
||||
EOF
|
||||
|
||||
if [ -n "${task_title}" ]; then
|
||||
|
||||
@@ -14,7 +14,7 @@ Keep `orch` as the control plane and use a leader-side bridge to launch a worker
|
||||
4. save the dispatch response:
|
||||
|
||||
```bash
|
||||
./assets/orch --db ./coord.db --json dispatch --run RUN_ID --task TASK_ID > TMPDIR/dispatch.json
|
||||
./assets/orch --db ./coord.db --json dispatch --run RUN_ID --task TASK_ID --execution-mode analysis > TMPDIR/dispatch.json
|
||||
```
|
||||
|
||||
5. render a standardized worker brief:
|
||||
|
||||
@@ -39,3 +39,4 @@ Rules:
|
||||
- The template is intentionally worker-only. Leader control stays with `orch`.
|
||||
- The generated brief should be passed into a spawned worker sub-agent together with `skills/inbox/`.
|
||||
- Keep the main-thread launch parameters in the leader workflow, not in the worker brief.
|
||||
- `execution_mode` is part of the explicit dispatch contract and should come from the saved dispatch payload, not from local heuristics.
|
||||
|
||||
Reference in New Issue
Block a user