Context
Recent commit 19e6e9a ("director: drop sub_dag_task_ids from PreparedBriefing, resolve from DAG") removed sub_dag_task_ids from the PreparedBriefing struct, the inline-by-Planner shape used when the Planner provides a prepared_briefing on a phase, skipping the Briefer agent. The motivation was supporting resume after partial completion: listing already-done tasks in a static slice was rejected by the validator, so the synthetic briefing now resolves its sub-DAG dynamically from live DAG state (every task in the phase still pending or needs_fix).
A later commit 9594dd9 ("docs: drop sub_dag_task_ids from plan schema and prune stale doc references") cleaned up the trailing schema reference in plan.schema.json that was still requiring the field on PreparedBriefing, plus stale doc references to deleted spec files.
Current state
sub_dag_task_ids is still alive in the runtime Briefing struct (the Briefer-emitted, per-iteration shape). Specifically, it is still present in:
internal/supervision/types.go:117, Briefing.SubDAGTaskIDs []string
internal/supervision/schemas/briefing.schema.json:8, required field
internal/supervision/schemas/mcp/briefing_emit.schema.json:13, required on briefing_emit input
internal/supervision/dag/handler.go:778, handler rejects empty sub_dag_task_ids on briefing_emit
internal/supervision/dag/handler.go:683, get_briefing returns it
internal/supervision/prompts/brief.md:25, Briefer prompt instructs to emit it
internal/supervision/prompts/review.md lines 8, 17, Reviewer prompt reads it via get_briefing and iterates
- Various tests, plus docs references in
docs/guides/director.md and docs/guides/director.pt-BR.md
So the two paths into the loop now disagree on how the sub-DAG is determined:
- Synthetic path (
PreparedBriefing from Planner): sub-DAG resolved dynamically from live DAG state.
- Briefer path (
Briefing emitted via briefing_emit): sub-DAG declared as an explicit static slice, validated by the handler.
This asymmetry has at least three observable consequences worth flagging:
- Two mental models for the same concept (what tasks does this iteration cover?).
- The resume problem that motivated
19e6e9a could in principle hit the Briefer path too: if a Briefer emits a sub_dag_task_ids slice that includes a task that just transitioned to done, the handler rejects the emit. Today the Briefer is prompted to only include pending/needs_fix, so it does not surface in practice, but it is a possible foot-gun.
- The field flows into the Reviewer prompt and shapes how it iterates acceptance. Removing it would require restructuring how the Reviewer learns which tasks are in scope for the current iteration.
Open question
Should the asymmetry be resolved by also dropping sub_dag_task_ids from the runtime Briefing, mirroring what 19e6e9a did for PreparedBriefing, OR is the asymmetry intentional (Briefer-emitted shape is the agent's declaration; synthetic-path resolution is a special case)?
This is a discussion issue, not a bug. No definitive resolution proposed.
Migration surface if we go the "remove" route
A future PR going down the "remove from runtime too" path would need to touch:
internal/supervision/dag/handler.go, the briefing_emit validation (line 778 empty-check and the surrounding sub-DAG checks around lines 780-792), the get_briefing response shape (line 683), and the approval-iteration code that consumes bs.briefing.SubDAGTaskIDs (lines 716-717, 1230-1231).
internal/supervision/schemas/briefing.schema.json and internal/supervision/schemas/mcp/briefing_emit.schema.json, drop the required field and its description.
internal/supervision/types.go, drop Briefing.SubDAGTaskIDs.
internal/supervision/prompts/brief.md, drop the instruction to emit it (line 25 and surrounding template).
internal/supervision/prompts/review.md, restructure how the Reviewer learns which tasks are in scope for the current iteration (lines 8 and 17 currently rely on the field).
- Tests under
internal/supervision/ and internal/supervision/dag/ that fixture or assert on sub_dag_task_ids.
- Docs:
docs/guides/director.md and docs/guides/director.pt-BR.md (the troubleshooting bullet at line 153 in each references the empty sub_dag_task_ids error).
Context
Recent commit
19e6e9a("director: drop sub_dag_task_ids from PreparedBriefing, resolve from DAG") removedsub_dag_task_idsfrom thePreparedBriefingstruct, the inline-by-Planner shape used when the Planner provides aprepared_briefingon a phase, skipping the Briefer agent. The motivation was supporting resume after partial completion: listing already-done tasks in a static slice was rejected by the validator, so the synthetic briefing now resolves its sub-DAG dynamically from live DAG state (every task in the phase stillpendingorneeds_fix).A later commit
9594dd9("docs: drop sub_dag_task_ids from plan schema and prune stale doc references") cleaned up the trailing schema reference inplan.schema.jsonthat was still requiring the field onPreparedBriefing, plus stale doc references to deleted spec files.Current state
sub_dag_task_idsis still alive in the runtimeBriefingstruct (the Briefer-emitted, per-iteration shape). Specifically, it is still present in:internal/supervision/types.go:117,Briefing.SubDAGTaskIDs []stringinternal/supervision/schemas/briefing.schema.json:8, required fieldinternal/supervision/schemas/mcp/briefing_emit.schema.json:13, required onbriefing_emitinputinternal/supervision/dag/handler.go:778, handler rejects emptysub_dag_task_idsonbriefing_emitinternal/supervision/dag/handler.go:683,get_briefingreturns itinternal/supervision/prompts/brief.md:25, Briefer prompt instructs to emit itinternal/supervision/prompts/review.mdlines 8, 17, Reviewer prompt reads it viaget_briefingand iteratesdocs/guides/director.mdanddocs/guides/director.pt-BR.mdSo the two paths into the loop now disagree on how the sub-DAG is determined:
PreparedBriefingfrom Planner): sub-DAG resolved dynamically from live DAG state.Briefingemitted viabriefing_emit): sub-DAG declared as an explicit static slice, validated by the handler.This asymmetry has at least three observable consequences worth flagging:
19e6e9acould in principle hit the Briefer path too: if a Briefer emits asub_dag_task_idsslice that includes a task that just transitioned todone, the handler rejects the emit. Today the Briefer is prompted to only includepending/needs_fix, so it does not surface in practice, but it is a possible foot-gun.Open question
Should the asymmetry be resolved by also dropping
sub_dag_task_idsfrom the runtimeBriefing, mirroring what19e6e9adid forPreparedBriefing, OR is the asymmetry intentional (Briefer-emitted shape is the agent's declaration; synthetic-path resolution is a special case)?This is a discussion issue, not a bug. No definitive resolution proposed.
Migration surface if we go the "remove" route
A future PR going down the "remove from runtime too" path would need to touch:
internal/supervision/dag/handler.go, thebriefing_emitvalidation (line 778 empty-check and the surrounding sub-DAG checks around lines 780-792), theget_briefingresponse shape (line 683), and the approval-iteration code that consumesbs.briefing.SubDAGTaskIDs(lines 716-717, 1230-1231).internal/supervision/schemas/briefing.schema.jsonandinternal/supervision/schemas/mcp/briefing_emit.schema.json, drop the required field and its description.internal/supervision/types.go, dropBriefing.SubDAGTaskIDs.internal/supervision/prompts/brief.md, drop the instruction to emit it (line 25 and surrounding template).internal/supervision/prompts/review.md, restructure how the Reviewer learns which tasks are in scope for the current iteration (lines 8 and 17 currently rely on the field).internal/supervision/andinternal/supervision/dag/that fixture or assert onsub_dag_task_ids.docs/guides/director.mdanddocs/guides/director.pt-BR.md(the troubleshooting bullet at line 153 in each references theempty sub_dag_task_idserror).