Skip to content

T-QJY7-orchestrate-grants-skill-tool-to-subagents

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

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

/sdlc:task-work Step 5a invokes /sdlc:task-ensure-ready via the Skill tool. When /sdlc:task-work is itself dispatched by /sdlc:orchestrate, the nested Skill tool call is denied — the dispatching context did not grant Skill to the sub-run. The agent falls back to inlining the ensure-ready logic (read implementation-ready.md, evaluate, stamp, commit), which works but diverges from the canonical implementation and loses the benefit of re-running a single source of truth.

/sdlc:task-work is in plugin/skills/task-work/SKILL.md, declaring allowed-tools: that includes Skill. When the user runs /sdlc:task-work directly, that authorization holds and Step 5a’s Skill invocation succeeds. When /sdlc:orchestrate dispatches /sdlc:task-work as part of one tick (see plugin/skills/orchestrate/SKILL.md), the dispatched run’s Skill calls return “Permission to use Skill has been denied” — observed on the 2026-05-20 run of T-UBJK-co-locate-skill-specific-scripts.

/sdlc:orchestrate adds Skill to its dispatched /sdlc:task-work sub-run’s tool grants. Today’s plugin/skills/orchestrate/SKILL.md:13-18 declares allowed-tools: [Bash, Read, Write, Agent, PushNotification] — no Skill. The dispatched sub-agent inherits the orchestrator’s grants (not task-work’s own allowed-tools: declaration), which is why nested Skill calls get denied. Adding Skill to the orchestrator’s grant list propagates the permission down to the sub-agent.

Rejected alternative: documenting an inline-fallback path in task-work. Skills compose by calling each other; the orchestrator should not be a permission downgrade.

  1. Add Skill to plugin/skills/orchestrate/SKILL.md’s allowed-tools: frontmatter list.
  2. Add a forbidden-phrase guard to plugin/skills/orchestrate/invariants.yaml that asserts Skill is present in the allowed-tools — prevents silent regression if someone trims the list later.
  3. Add a regression case to plugin/skills/orchestrate/tests/run_evals.py (or equivalent) that asserts the frontmatter allowed-tools list contains Skill.
  • plugin/skills/orchestrate/SKILL.md — add Skill to allowed-tools:.
  • plugin/skills/orchestrate/invariants.yaml — add the presence-guard.
  • plugin/skills/orchestrate/tests/run_evals.py — add the frontmatter-check regression case.
  • AC-1: A fresh /sdlc:orchestrate tick dispatching a ready task into /sdlc:task-work runs /sdlc:task-ensure-ready via the Skill tool without permission denial.
  • AC-2: The dispatched run still respects the one-line-verdict contract on completion.
  • AC-3: plugin/skills/orchestrate/tests/run_evals.py includes a case that loads orchestrate/SKILL.md’s frontmatter and asserts Skill is in allowed-tools. Failing to list it fails the test.
  • Broader allowed-tools propagation semantics (e.g. inheritance rules for arbitrary nesting depth). This task only adds the single Skill grant the dispatched task-work needs.
  • Reform of how task-work’s own allowed-tools: is honored when invoked top-level vs as a sub-agent — see 2026-05-21-investigate-skill-prompt-freshness for that surface.
  • none

Spawned by /sdlc:task-work post-mortem of T-UBJK-co-locate-skill-specific-scripts on 2026-05-20.

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

  • AC-1: deferred-user — requires a fresh /sdlc:orchestrate tick dispatching a ready task to confirm the nested /sdlc:task-ensure-ready Skill call succeeds end-to-end. The grant is in place (plugin/skills/orchestrate/SKILL.md allowed-tools: now contains Skill), but verifying the runtime behavior requires live orchestrator dispatch which isn’t reachable from inside this task-work run.
  • AC-2: deferred-user — same live-dispatch dependency. The one-line-verdict contract is enforced by orchestrate’s truncation guard, which is exercised by the existing 8-case eval suite in run_evals.py; the property doesn’t regress with this change, but the integrated path (orchestrate → task-work → ensure-ready, return verdict) needs a live tick to confirm.
  • AC-3: auto — plugin/skills/orchestrate/tests/run_evals.py now loads SKILL.md’s frontmatter, parses allowed-tools:, and asserts Skill is in the list. Verified to PASS as-is, FAIL when Skill is stripped, and PASS again on restore (regression-test demonstration).
  • The presence-guard property had two independent enforcement paths available: the existing invariants.yaml lint mechanism and a new check in the eval suite. Both landed cleanly with small diffs.
  • extract_allowed_tools is dependency-free (no yaml import) so the PEP-723 self-bootstrapping script header keeps its empty dependencies = [] list — no new install cost.
  • The Step 5b rebase surfaced a trivial conflict because Step 5a’s feat-branch readiness-stamp commit touched the same last_reviewed: / readiness_verified_at: lines that Step 5b’s main commit then updated. The conflict was mechanical and the resolution obvious (take main’s newer values), but a task-work helper that knows the fixed three-line shape and resolves it automatically would remove a manual touch point from every run. → T-H98A-last-reviewed-rebase-collision
  • The task spec said “forbidden-phrase guard that asserts Skill is present” — that phrasing is internally contradictory (forbidden phrases enforce absence; presence is a required_phrases concern). Implementation inferred the intent correctly, but a tighter spec template that distinguishes presence vs. absence guards by name would prevent the same ambiguity on future invariants-touching tasks. → skipped (no concrete template artifact to change; this was a hand-written spec, not output of a generator).

← Back to Tasks