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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions .opencode/agents/dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ Execute each step in `phase` order.

> Hook will auto-inject all specs, requirements, and technical design to subagent context.
> Dispatch only needs to issue simple call commands.
>
> **OpenCode dispatch rule**: Call subagents synchronously (`run_in_background: false`).
> Do NOT use `TaskOutput` or background polling as the completion signal for child phases.
> The background wrapper can finish before the real subagent session is actually done.

### action: "implement"

Expand All @@ -79,7 +83,7 @@ Task(
subagent_type: "implement",
prompt: "Implement the feature described in prd.md in the task directory",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -98,7 +102,7 @@ Task(
subagent_type: "check",
prompt: "Check code changes, fix issues yourself",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -116,7 +120,7 @@ Task(
subagent_type: "debug",
prompt: "Fix the issues described in the task context",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -132,7 +136,7 @@ Task(
subagent_type: "check",
prompt: "[finish] Execute final completion check before PR",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand Down Expand Up @@ -168,35 +172,31 @@ This will:
### Basic Pattern

```
task_id = Task(
result = Task(
subagent_type: "implement", // or "check", "debug"
prompt: "Simple task description",
model: "opus",
run_in_background: true
run_in_background: false
)

// Poll for completion
for i in 1..N:
result = TaskOutput(task_id, block=true, timeout=300000)
if result.status == "completed":
break
// Wait for the Task call to return before starting the next phase.
// Do NOT call TaskOutput or use background polling inside OpenCode dispatch.
```

### Timeout Settings
### Execution Rule

| Phase | Max Time | Poll Count |
|-------|----------|------------|
| implement | 30 min | 6 times |
| check | 15 min | 3 times |
| debug | 20 min | 4 times |
- Run one phase at a time
- Start the next phase only after the current `Task(...)` call returns
- If a phase returns a clear timeout or failure, handle that result explicitly
- Do **not** simulate completion by polling a background task wrapper

---

## Error Handling

### Timeout

If a subagent times out, notify the user and ask for guidance:
If a synchronous subagent call times out, notify the user and ask for guidance:

```
"Subagent {phase} timed out after {time}. Options:
Expand All @@ -207,10 +207,11 @@ If a subagent times out, notify the user and ask for guidance:

### Subagent Failure

If a subagent reports failure, read the output and decide:
If a synchronous subagent call reports failure, read the output and decide:

- If recoverable: call debug agent to fix
- If not recoverable: notify user and ask for guidance
- Do not switch back to `TaskOutput` polling for the same phase

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ trellis init --opencode -u your-name

- **Works**: Workspace, tasks, specs, agents, commands
- **Note**: Full subagent context injection requires [oh-my-opencode](https://github.com/nicepkg/oh-my-opencode). Without it, agents use Self-Loading fallback.
- **Dispatch rule**: Trellis OpenCode dispatch should call child agents synchronously. Do not treat background task status alone as proof that a child session has fully finished.

### Codex

Expand Down
39 changes: 20 additions & 19 deletions packages/cli/src/templates/opencode/agents/dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ Execute each step in `phase` order.

> Hook will auto-inject all specs, requirements, and technical design to subagent context.
> Dispatch only needs to issue simple call commands.
>
> **OpenCode dispatch rule**: Call subagents synchronously (`run_in_background: false`).
> Do NOT use `TaskOutput` or background polling as the completion signal for child phases.
> The background wrapper can finish before the real subagent session is actually done.

### action: "implement"

Expand All @@ -79,7 +83,7 @@ Task(
subagent_type: "implement",
prompt: "Implement the feature described in prd.md in the task directory",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -98,7 +102,7 @@ Task(
subagent_type: "check",
prompt: "Check code changes, fix issues yourself",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -116,7 +120,7 @@ Task(
subagent_type: "debug",
prompt: "Fix the issues described in the task context",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand All @@ -132,7 +136,7 @@ Task(
subagent_type: "check",
prompt: "[finish] Execute final completion check before PR",
model: "opus",
run_in_background: true
run_in_background: false
)
```

Expand Down Expand Up @@ -168,35 +172,31 @@ This will:
### Basic Pattern

```
task_id = Task(
result = Task(
subagent_type: "implement", // or "check", "debug"
prompt: "Simple task description",
model: "opus",
run_in_background: true
run_in_background: false
)

// Poll for completion
for i in 1..N:
result = TaskOutput(task_id, block=true, timeout=300000)
if result.status == "completed":
break
// Wait for the Task call to return before starting the next phase.
// Do NOT call TaskOutput or use background polling inside OpenCode dispatch.
```

### Timeout Settings
### Execution Rule

| Phase | Max Time | Poll Count |
|-------|----------|------------|
| implement | 30 min | 6 times |
| check | 15 min | 3 times |
| debug | 20 min | 4 times |
- Run one phase at a time
- Start the next phase only after the current `Task(...)` call returns
- If a phase returns a clear timeout or failure, handle that result explicitly
- Do **not** simulate completion by polling a background task wrapper

---

## Error Handling

### Timeout

If a subagent times out, notify the user and ask for guidance:
If a synchronous subagent call times out, notify the user and ask for guidance:

```
"Subagent {phase} timed out after {time}. Options:
Expand All @@ -207,10 +207,11 @@ If a subagent times out, notify the user and ask for guidance:

### Subagent Failure

If a subagent reports failure, read the output and decide:
If a synchronous subagent call reports failure, read the output and decide:

- If recoverable: call debug agent to fix
- If not recoverable: notify user and ask for guidance
- Do not switch back to `TaskOutput` polling for the same phase

---

Expand Down
28 changes: 28 additions & 0 deletions packages/cli/test/regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ import { guidesIndexContent, workspaceIndexContent } from "../src/templates/mark
import * as markdownExports from "../src/templates/markdown/index.js";
import { TrellisContext } from "../src/templates/opencode/lib/trellis-context.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const opencodeDispatchTemplate = fs.readFileSync(
path.join(__dirname, "../src/templates/opencode/agents/dispatch.md"),
"utf-8",
);

afterEach(() => {
clearManifestCache();
});
Expand Down Expand Up @@ -669,6 +676,27 @@ describe("regression: update only configured platforms (beta.16)", () => {
});
});

describe("regression: OpenCode dispatch waits for child sessions (issue-149)", () => {
it("[issue-149] OpenCode dispatch uses synchronous Task calls for child phases", () => {
expect(opencodeDispatchTemplate).toContain("run_in_background: false");
expect(opencodeDispatchTemplate).not.toContain("run_in_background: true");
});

it("[issue-149] OpenCode dispatch forbids TaskOutput polling", () => {
expect(opencodeDispatchTemplate).toContain("Do NOT call TaskOutput");
expect(opencodeDispatchTemplate).not.toContain("TaskOutput(task_id");
});

it("[issue-149] OpenCode dispatch instructs phase-by-phase blocking execution", () => {
expect(opencodeDispatchTemplate).toContain(
"Wait for the Task call to return before starting the next phase.",
);
expect(opencodeDispatchTemplate).toContain(
"Start the next phase only after the current `Task(...)` call returns",
);
});
});

// =============================================================================
// 4. Template Integrity Regressions
// =============================================================================
Expand Down