Problem
devkit_start returns a per-step prompt and the instruction "call devkit_advance when this step is complete," but nothing actually enforces that. The engine can reject out-of-order advances (via enforce: hard), but it can't force the agent to come back at all — there's no mechanism for an MCP server to veto the agent's next tool call. Once the first step is returned, the agent can make arbitrary unrelated tool calls, skip the remaining 5 steps of a 6-step workflow, and the session just sits running forever.
This was discovered during a PR #62 mega-review where the tri-review workflow's step-1 gather prompt was returned, and then all subsequent steps were silently skipped until it was noticed mid-conversation. The determinism work (24 skills → 8 YAML workflows in the engine) correctly made step ordering, gates, and loops deterministic inside a workflow — but the outer "force the agent to actually use the workflow" loop was never closed.
Proposed architecture
PreToolUse hook that reads devkit session state and blocks non-devkit tool calls when a workflow session is running and the current step is un-advanced.
Components:
-
Engine CLI flag: devkit-engine session-status [--session <id>] that prints active sessions from the state store as JSON. Read-only, no MCP round-trip needed. Lets a shell hook introspect the engine without depending on the stdio transport.
-
New hook script: hooks/devkit-workflow-guard.sh — runs as PreToolUse, calls devkit-engine session-status, and if any session is running:
- Allow:
mcp__plugin_devkit_devkit-engine__devkit_advance, devkit_status, and a small read-only allowlist (Read, Grep, Glob, Bash gated on a non-destructive set?) so the agent can gather evidence for the step output.
- Block everything else with a clear veto message:
devkit workflow <name> is at step N/M — call devkit_advance before other tool calls.
-
Hook registration: add the new hook to the plugin's hooks manifest so it runs on every PreToolUse.
Acceptance criteria
- After
devkit_start, calling any non-allowlisted tool returns a hook veto with the step number + workflow name in the message.
devkit_advance correctly advances the step and re-checks the block condition.
- Session completion (
workflow complete) clears the block and normal tool access resumes.
- Parallel workflows (e.g.,
mega-pr dispatching tri-review + pr-review-toolkit) either both run to completion or the parent skill only dispatches one at a time — figure out which during design.
- Unit tests for the allowlist logic + an integration test that exercises the full "start → try to call disallowed tool → blocked → advance → unblock" path.
Out of scope
Context
Discovered in the PR #62 mega-review conversation when 5/6 steps of the tri-review workflow were silently skipped. The engine behaved correctly (returned step 1 prompt, waited for advance); I just never called devkit_advance. A hook is the only layer that can actually veto the next tool call — MCP servers structurally cannot.
Problem
devkit_startreturns a per-step prompt and the instruction "calldevkit_advancewhen this step is complete," but nothing actually enforces that. The engine can reject out-of-order advances (viaenforce: hard), but it can't force the agent to come back at all — there's no mechanism for an MCP server to veto the agent's next tool call. Once the first step is returned, the agent can make arbitrary unrelated tool calls, skip the remaining 5 steps of a 6-step workflow, and the session just sitsrunningforever.This was discovered during a PR #62 mega-review where the tri-review workflow's step-1 gather prompt was returned, and then all subsequent steps were silently skipped until it was noticed mid-conversation. The determinism work (24 skills → 8 YAML workflows in the engine) correctly made step ordering, gates, and loops deterministic inside a workflow — but the outer "force the agent to actually use the workflow" loop was never closed.
Proposed architecture
PreToolUse hook that reads devkit session state and blocks non-devkit tool calls when a workflow session is running and the current step is un-advanced.
Components:
Engine CLI flag:
devkit-engine session-status [--session <id>]that prints active sessions from the state store as JSON. Read-only, no MCP round-trip needed. Lets a shell hook introspect the engine without depending on the stdio transport.New hook script:
hooks/devkit-workflow-guard.sh— runs as PreToolUse, callsdevkit-engine session-status, and if any session isrunning:mcp__plugin_devkit_devkit-engine__devkit_advance,devkit_status, and a small read-only allowlist (Read,Grep,Glob,Bashgated on a non-destructive set?) so the agent can gather evidence for the step output.devkit workflow <name> is at step N/M — call devkit_advance before other tool calls.Hook registration: add the new hook to the plugin's hooks manifest so it runs on every PreToolUse.
Acceptance criteria
devkit_start, calling any non-allowlisted tool returns a hook veto with the step number + workflow name in the message.devkit_advancecorrectly advances the step and re-checks the block condition.workflow complete) clears the block and normal tool access resumes.mega-prdispatching tri-review + pr-review-toolkit) either both run to completion or the parent skill only dispatches one at a time — figure out which during design.Out of scope
enforce: hardsemantics inside the engine itself; the engine is fine, the gap is discipline outside the loop.Context
Discovered in the PR #62 mega-review conversation when 5/6 steps of the tri-review workflow were silently skipped. The engine behaved correctly (returned step 1 prompt, waited for advance); I just never called
devkit_advance. A hook is the only layer that can actually veto the next tool call — MCP servers structurally cannot.