15 KiB
Wrapper Draft Next Actions Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add advisory next_actions to wf.workflow.create_draft_workspace_from_capability so LLM clients can continue safely even when they cannot easily read the docs.
Architecture: Keep wrapper_hints as the source of truth for scaffold confidence, missing decisions, and mapping warnings. Add a small typed result object that converts those hints into concrete next-tool guidance. can_save_now is advisory only; do not block saving or enforce policy in this slice.
Tech Stack: Python 3.14, Pydantic v2, FastMCP tool schemas, pytest, ruff, basedpyright.
Scope
Add next_actions to CreateDraftWorkspaceFromCapabilityResult and handler payloads.
Do:
- Return machine-readable guidance from
create_draft_workspace_from_capability. - Make
can_save_nowadvisory only. - Recommend
wf.workflow.patch_draft_workspacewhenwrapper_hints.missing_decisionsis non-empty or confidence is low. - Recommend
wf.workflow.validate_draft_workspacewhen the scaffold looks safe enough to validate. - Include patch examples for common missing decisions.
- Document the field and add schema tests.
Do not:
- Block
create_artifact_from_workspace. - Add a new tool.
- Infer raw MCP
content[0].textautomatically. - Treat boolean output candidates as real routing semantics.
- Replace
wrapper_hints;next_actionsshould summarize and guide, not duplicate every hint.
Files
- Modify:
src/wf_mcp/workflow_surface/models.py- Add typed
WrapperDraftNextActionsandWrapperDraftPatchExamplePydantic models. - Add
next_actionsfield toCreateDraftWorkspaceFromCapabilityResult.
- Add typed
- Modify:
src/wf_mcp/workflow_surface/handlers.py- Add helper that derives
next_actionsfromwrapper_hintsand workspace id/revision. - Include
next_actionsincreate_draft_workspace_from_capabilityresult.
- Add helper that derives
- Test:
tests/wf_mcp/workflow_surface/test_drafts.py- Assert high-confidence draft returns validate/save guidance.
- Assert low-confidence content-block draft returns patch guidance and advisory
can_save_now=false.
- Test:
tests/wf_mcp/server/test_config.py- Assert output schema exposes
next_actionswith field descriptions.
- Assert output schema exposes
- Modify:
docs/workflow_capabilities.md- Explain
next_actionsas advisory continuation hints.
- Explain
- Modify:
docs/wf_mcp_end_to_end_runbook.md- Mention that clients can follow
next_actionsafter draft creation.
- Mention that clients can follow
Data Shape
Add this output shape:
{
"next_actions": {
"can_save_now": true,
"recommended_next_tool": "wf.workflow.validate_draft_workspace",
"reason": "Wrapper hints are high confidence and have no missing decisions.",
"patch_examples": [],
"warnings": []
}
}
Low-confidence example:
{
"next_actions": {
"can_save_now": false,
"recommended_next_tool": "wf.workflow.patch_draft_workspace",
"reason": "Review missing wrapper decisions before saving.",
"patch_examples": [
{
"description": "Replace the output bindings after choosing workflow state fields.",
"tool": "wf.workflow.patch_draft_workspace",
"request": {
"workspace_id": "echo_wrapper",
"revision": 1,
"patch": [
{
"op": "replace",
"path": "/draft/steps/call/output",
"value": []
}
]
}
}
],
"warnings": [
"Raw MCP content blocks are not workflow-shaped. Use an explicit wrapper or extraction node."
]
}
}
Task 1: Add Result Models And Schema Test
Files:
-
Modify:
src/wf_mcp/workflow_surface/models.py -
Modify:
tests/wf_mcp/server/test_config.py -
Step 1: Add failing schema assertions
In tests/wf_mcp/server/test_config.py, inside the test that already inspects create_draft_workspace_from_capability output schema, add:
assert "next_actions" in from_capability_output["properties"]
next_actions_schema = from_capability_output["properties"]["next_actions"]
assert "recommended_next_tool" in next_actions_schema["properties"]
assert "patch_examples" in next_actions_schema["properties"]
assert "advisory" in next_actions_schema["properties"]["can_save_now"]["description"]
Run:
uv run pytest tests/wf_mcp/server/test_config.py -q
Expected: fail because next_actions is not in the output schema.
- Step 2: Add Pydantic result models
In src/wf_mcp/workflow_surface/models.py, add near CreateDraftWorkspaceFromCapabilityResult:
class WrapperDraftPatchExample(BaseModel):
"""Concrete patch-workspace example for a likely next authoring edit."""
description: str = Field(description="Human-readable reason for this patch.")
tool: str = Field(description="MCP tool to call for this example.")
request: dict[str, Any] = Field(
description="JSON request payload to pass to the tool."
)
class WrapperDraftNextActions(BaseModel):
"""Advisory continuation hints after bootstrapping a wrapper draft."""
can_save_now: bool = Field(
description=(
"Advisory only. False means the scaffold likely needs review before "
"saving, but the server does not enforce this."
)
)
recommended_next_tool: str = Field(
description=(
"Suggested next MCP tool, usually wf.workflow.validate_draft_workspace "
"or wf.workflow.patch_draft_workspace."
)
)
reason: str = Field(description="Short explanation for the recommendation.")
patch_examples: list[WrapperDraftPatchExample] = Field(
default_factory=list,
description="Concrete JSON Patch examples for common missing decisions.",
)
warnings: list[str] = Field(
default_factory=list,
description="Non-blocking warnings copied from low-confidence wrapper hints.",
)
Then update:
class CreateDraftWorkspaceFromCapabilityResult(DraftWorkspaceResult):
"""Draft workspace result plus wrapper hints and advisory next actions."""
wrapper_hints: dict[str, Any] = Field(...)
next_actions: WrapperDraftNextActions = Field(
description=(
"Advisory next step guidance derived from wrapper_hints. "
"The server does not enforce can_save_now."
)
)
- Step 3: Run schema test
Run:
uv run pytest tests/wf_mcp/server/test_config.py -q
Expected: schema assertion passes, but runtime tests may still fail later until handler returns next_actions.
Task 2: Derive Next Actions In The Handler
Files:
-
Modify:
src/wf_mcp/workflow_surface/handlers.py -
Modify:
tests/wf_mcp/workflow_surface/test_drafts.py -
Step 1: Add high-confidence behavior test
In tests/wf_mcp/workflow_surface/test_drafts.py, extend test_workflow_surface_creates_draft_workspace_from_capability_hints:
next_actions = result["next_actions"]
assert next_actions["can_save_now"] is True
assert next_actions["recommended_next_tool"] == "wf.workflow.validate_draft_workspace"
assert "high confidence" in next_actions["reason"]
assert next_actions["patch_examples"] == []
assert next_actions["warnings"] == []
Run:
uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py::test_workflow_surface_creates_draft_workspace_from_capability_hints -q
Expected: fail because handler does not return next_actions.
- Step 2: Add low-confidence behavior test
Find or create a content-block capability test near existing wrapper hint tests. Use an output schema shaped like:
content_output_schema = {
"type": "object",
"properties": {
"content": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {"type": "string"},
"text": {"type": "string"},
},
},
}
},
}
Register a NodeSpec/capability with that output schema, call create_draft_workspace_from_capability, then assert:
next_actions = result["next_actions"]
assert next_actions["can_save_now"] is False
assert next_actions["recommended_next_tool"] == "wf.workflow.patch_draft_workspace"
assert "missing wrapper decisions" in next_actions["reason"]
assert next_actions["patch_examples"][0]["tool"] == "wf.workflow.patch_draft_workspace"
assert next_actions["patch_examples"][0]["request"]["workspace_id"] == "content_wrapper"
assert next_actions["patch_examples"][0]["request"]["revision"] == result["revision"]
assert next_actions["warnings"]
Run:
uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py -q
Expected: fail until implementation exists.
- Step 3: Implement helper in handler
In src/wf_mcp/workflow_surface/handlers.py, add a helper near other draft helpers:
def _wrapper_draft_next_actions(
*,
workspace_id: str,
revision: int,
hints: dict[str, Any],
) -> dict[str, Any]:
"""Convert wrapper_hints into advisory next-tool guidance for MCP clients."""
confidence = str(hints.get("confidence", "low"))
missing_decisions = hints.get("missing_decisions")
notes = [str(note) for note in hints.get("notes", []) if isinstance(note, str)]
has_missing = isinstance(missing_decisions, list) and len(missing_decisions) > 0
can_save_now = confidence == "high" and not has_missing
if can_save_now:
return {
"can_save_now": True,
"recommended_next_tool": "wf.workflow.validate_draft_workspace",
"reason": "Wrapper hints are high confidence and have no missing decisions.",
"patch_examples": [],
"warnings": [],
}
return {
"can_save_now": False,
"recommended_next_tool": "wf.workflow.patch_draft_workspace",
"reason": "Review missing wrapper decisions before saving.",
"patch_examples": _wrapper_draft_patch_examples(
workspace_id=workspace_id,
revision=revision,
hints=hints,
),
"warnings": notes,
}
Add:
def _wrapper_draft_patch_examples(
*,
workspace_id: str,
revision: int,
hints: dict[str, Any],
) -> list[dict[str, Any]]:
"""Return conservative JSON Patch examples without guessing semantics."""
examples: list[dict[str, Any]] = []
missing_decisions = hints.get("missing_decisions")
if not isinstance(missing_decisions, list):
return examples
decision_kinds = {
str(decision.get("kind"))
for decision in missing_decisions
if isinstance(decision, dict)
}
if {
"choose_output_fields",
"review_nested_output",
} & decision_kinds:
examples.append(
{
"description": (
"Replace output bindings after choosing which capability "
"outputs should be written to workflow state."
),
"tool": "wf.workflow.patch_draft_workspace",
"request": {
"workspace_id": workspace_id,
"revision": revision,
"patch": [
{
"op": "replace",
"path": "/draft/steps/call/output",
"value": [],
}
],
},
}
)
if "confirm_boolean_outcomes" in decision_kinds:
examples.append(
{
"description": (
"Review boolean output candidates before adding routing; "
"do not route on boolean fields automatically."
),
"tool": "wf.workflow.patch_draft_workspace",
"request": {
"workspace_id": workspace_id,
"revision": revision,
"patch": [],
},
}
)
return examples
Then change the return in create_draft_workspace_from_capability:
return {
**result,
"wrapper_hints": hints,
"next_actions": _wrapper_draft_next_actions(
workspace_id=workspace_id,
revision=int(result["revision"]),
hints=hints,
),
}
- Step 4: Run behavior tests
Run:
uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py -q
Expected: pass.
Task 3: Document Next Actions
Files:
-
Modify:
docs/workflow_capabilities.md -
Modify:
docs/wf_mcp_end_to_end_runbook.md -
Step 1: Update workflow capability docs
In docs/workflow_capabilities.md, near the wrapper_hints section, add:
`create_draft_workspace_from_capability` also returns `next_actions`.
This is advisory guidance for clients that cannot easily read the full docs.
It summarizes whether the scaffold is safe-looking enough to validate, which
tool to call next, and concrete patch examples for common missing decisions.
`next_actions.can_save_now` is not enforced. A caller can still save a low
confidence draft, but the field exists to make that risk explicit.
- Step 2: Update runbook
In docs/wf_mcp_end_to_end_runbook.md, near the draft-from-capability example, add:
After `create_draft_workspace_from_capability`, inspect `next_actions`.
If `recommended_next_tool` is `wf.workflow.patch_draft_workspace`, apply or
adapt the returned `patch_examples` before saving. If it recommends
`wf.workflow.validate_draft_workspace`, validate the draft before creating an
artifact.
- Step 3: Grep docs
Run:
rg -n "next_actions|can_save_now|patch_examples" docs/workflow_capabilities.md docs/wf_mcp_end_to_end_runbook.md
Expected: all three terms appear.
Task 4: Final Verification
Files:
-
All touched files.
-
Step 1: Run focused tests
Run:
uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py tests/wf_mcp/test_workflow_wrapper_hints.py -q
Expected: pass.
- Step 2: Run touched-file lint and format check
Run:
uv run ruff check src/wf_mcp/workflow_surface/models.py src/wf_mcp/workflow_surface/handlers.py tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py
uv run ruff format --check src/wf_mcp/workflow_surface/models.py src/wf_mcp/workflow_surface/handlers.py tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py
Expected: pass.
- Step 3: Run touched-file type check
Run:
uv run basedpyright --level error src/wf_mcp/workflow_surface/models.py src/wf_mcp/workflow_surface/handlers.py tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py
Expected: 0 errors.
- Step 4: Optional full suite
Run when time allows:
uv run pytest -q
Expected current baseline: full suite passes with the existing skip/xfail count.
Notes For Opencode
can_save_nowis advisory. Do not enforce it.- Do not add a new save gate.
- Do not generate semantic routes from boolean fields.
- Keep patch examples conservative; empty patch examples are acceptable when the missing decision cannot be safely represented.
- Avoid whole-dict assertions. Assert individual fields.
- Existing docs may be long; keep additions short.