Skip to content

T-Y0SQ-task-work-emit-completion-marker

Status: closed/done · Impact: high · Complexity: small

Auto-generated from a /sdlc:task-work post-mortem. Review and promote to ready before picking up.

When an orchestrator dispatches /sdlc:task-work to a sub-agent, the sub-agent has no unambiguous signal for “the whole flow finished”. It sees READY: <basename> emitted by /sdlc:task-ensure-ready in Step 5a and treats that as the end of the run, even though Steps 5b through 10 still need to execute. This has happened twice on T-ZQ4H-task-ensure-ready-accepts-in-progress alone — the sub- agent returned early both times. Closing this gap means task-work emits one final stdout marker whose absence is unambiguous evidence the sub-agent stopped early, so orchestrators can detect partial runs automatically.

plugin/skills/task-work/SKILL.md ends with Step 10 (“Open the PR”) followed by Step 11 (“After the PR merges — close the loop”) and Step 12 (“Failure modes”). None of those steps prescribe a single final stdout line. Sub-agents that quote stdout markers back to their parent have no way to distinguish “I finished” from “I returned in the middle”. The intermediate READY: from ensure-ready (Step 5a) looks plausibly terminal because it’s the first stdout marker in the flow.

/sdlc:task-work emits a single, distinctive marker as its last stdout line — e.g. TASK-WORK COMPLETE: <basename> PR=<url> — at the successful end of Step 10. Orchestrators check for this marker before trusting the sub-agent’s report. Absence of the marker means the sub-agent stopped early; the orchestrator can re-dispatch with a “continue from ” brief.

  1. Add a “Final marker” subsection at the end of plugin/skills/task- work/SKILL.md Step 10 specifying the exact stdout line.
  2. Pin the literal marker text in plugin/skills/task-work/invariants. yaml so lint_skill_prose.py enforces its presence.
  3. Add a note to Step 5a (/sdlc:task-ensure-ready returns READY:) explicitly saying “this is NOT the end of the flow — continue to Step 5b” so future sub-agent readings don’t conflate the two markers.
  • plugin/skills/task-work/SKILL.md — add the final-marker section and the early-exit warning.
  • plugin/skills/task-work/invariants.yaml — pin the marker phrase.
  • AC-1: /sdlc:task-work’s SKILL.md prescribes an exact final stdout line at the end of Step 10.
  • AC-2: lint_skill_prose.py enforces that exact phrase as a required-phrase invariant.
  • AC-3: Step 5a’s prose names the READY: marker explicitly as “intermediate, not terminal”.
  • Wiring the marker through the Agent-tool sub-agent harness — that’s a separate orchestration change.
  • none

Spawned by /sdlc:task-work post-mortem of T-ZQ4H-task-ensure-ready-accepts-in-progress on 2026-05-19.

Captured by /sdlc:task-work on 2026-05-20. PR: pending.

  • AC-1: auto — the new “Final marker” subsection in Step 10 contains the literal line TASK-WORK COMPLETE: <basename> PR=<pr-url> inside a fenced code block, and lint_skill_prose.py confirms its presence as a required phrase pinned to Step 10.
  • AC-2: auto — plugin/scripts/lint_skill_prose.py run against plugin/skills/task-work/SKILL.md reports no violations. The invariants.yaml now pins both TASK-WORK COMPLETE: <basename> PR=<pr-url> (section “10.”) and intermediate, not terminal (section “5a.”).
  • AC-3: auto — Step 5a now contains a bolded paragraph that names READY: explicitly as “intermediate, not terminal” and tells sub-agents an orchestrator should re-dispatch when only the intermediate marker is seen. The phrase is lint-pinned to Step 5a.
  • The existing invariants.yaml + lint_skill_prose.py machinery made AC-2 trivially mechanical: add one required-phrase entry per ACs that asks for literal prose, and the linter is the verification.
  • The task spec named the exact marker text it wanted, so there was no design decision to defer.
  • The skill prompt rendered to the runner contained just full-check / just ci references at Step 7, but the live SKILL.md file already uses ${CLAUDE_PLUGIN_ROOT}scripts/run_quality_checks.py. The discrepancy cost one trip through “what does this project actually use?” The gap is upstream of this task: the skill prompt delivered to a child invocation should reflect the file’s current state, not a snapshot. Worth a follow-up to investigate skill-prompt freshness when /sdlc:task-work is invoked from another skill — does the Skill tool re-read the file each call, or cache? → 2026-05-21-investigate-skill-prompt-freshness
  • The post-mortem template instructs the runner to spawn a sub-agent for follow-up tasks even when gaps are - none observed. For a one- or two-line gap list this is overhead — the spawn-from-post-mortem procedure already supports the no-op case, but in environments where the Agent/Task tool isn’t available (this run had no such tool exposed) the runner has to inline-execute the procedure manually. Worth an inline fast-path when the friction list is empty or trivially short, plus an explicit fallback contract for when the Agent tool isn’t available. → T-TZD2-task-work-spawn-fast-path-no-op
  • T-TZD2-task-work-spawn-fast-path-no-op — inline fast-path for empty post-mortem friction lists, plus documented fallback when the Agent tool isn’t available (created).
  • 2026-05-21-investigate-skill-prompt-freshness — investigate whether sub-skill prompt delivery re-reads SKILL.md each call or caches stale prose (created).

← Back to Tasks