Making Amplifier's pipeline structural, not advisory
Amplifier's mode system already gates what happens inside a mode — blocking deploy, requiring approval to write. But nothing governed how an agent moved between modes. The intended pipeline was documentation, not a guarantee.
So how open was that gap, really?
With allowed_transitions absent (default None) any mode could activate any other. With allow_clear defaulting to True, any agent could /mode off straight out of a workflow.
Those permissive defaults have real consequences for a real workflow.
Picture an intended plan → implement sequence written only in a mode body. Nothing stopped an agent from jumping straight to implement, or dropping to modeless mid-task.
A workflow you can't enforce isn't a workflow. Enter two fields.
In the mode tool's set handler, an unlisted target returns a hard transition_denied error — and active_mode is left unchanged. Forward movement is now constrained structurally.
That seals forward jumps. But there's still a back door.
In the clear handler, mode(clear) / /mode off is rejected with a clear_denied error — forcing an explicit move to a valid next mode instead of a silent exit.
Both doors are shut. But does the enforcement actually bite?
The gate_policy defaults to 'warn'. But the transition check runs before the gate policy is applied — so a denied jump is a hard transition_denied error, not a soft warning, even under that permissive default.
This is the moment advisory becomes structural.
The merged worked example proves it: plan may only go to implement and can't clear until it does; implement may go back to plan and can exit when done. A workflow that can no longer be skipped.
And any author can adopt the pattern.
The two enforcement fields complement prose, they don't replace it: the mode body tells the LLM which transition is appropriate; the frontmatter enforces which is permitted. “They are complementary — don't pick one.”
Optional and backward-compatible — the permissive defaults preserve every existing mode.
Data as of: 2026-03-04 (feature commits) — independently fact-checked (verdict: pass).
Repository: ramparte/amplifier-bundle-modes (upstream microsoft/amplifier-bundle-modes).
Feature: allowed_transitions & allow_clear mode-transition enforcement (merge commit 36b77c0, ancestor of HEAD and tag v1.3.1).
Commands run:
grep -n 'allowed_transitions\|allow_clear' modules/hooks-mode/amplifier_module_hooks_mode/__init__.py — ModeDefinition dataclass fields (lines 81–82).sed -n '238,275p' modules/tool-mode/amplifier_module_tool_mode/__init__.py — _handle_set emits transition_denied, active_mode unchanged.sed -n '427,462p' modules/tool-mode/amplifier_module_tool_mode/__init__.py — _handle_clear emits clear_denied.sed -n '887,960p' context/mode-schema-reference.md — §9.5 plan/implement worked pair.sed -n '300,315p' skills/mode-design-discipline/SKILL.md — “the body narrates, the field enforces”.uv run --with pytest-asyncio pytest tests/test_tool_mode.py -k 'Transition or Clear' -q — 23 passed, 1 failed, 27 deselected.git log / git show --stat / git shortlog -sne --all / git tag — timeline, authorship, latest tag.Verification: 50/51 tool-mode tests pass; all 23 transition/clear enforcement tests pass. The sole failure (TestClearedEvent) is an unrelated mode:cleared event-payload assertion, outside the enforcement paths.
Gaps: No PR body / GitHub metadata fetched via gh (offline, local git only); PR #6 title/number taken from the local merge-commit message. The illustrative “brainstorm→finish” jump is not the shipped example — the plan/implement pair (§9.5) is.
Primary contributor: Brian Krabach — authored all six enforcement commits (a7eb8fe..36b77c0) and merged PR #6.