239 lines
6.8 KiB
Markdown
239 lines
6.8 KiB
Markdown
# Workflow Surface Next Actions Design
|
|
|
|
## Purpose
|
|
|
|
`next_actions` is becoming a reusable UX pattern for MCP-facing workflow tools.
|
|
It gives LLM clients a small, machine-readable answer to:
|
|
|
|
```text
|
|
What should I call next?
|
|
```
|
|
|
|
This is especially useful when a client cannot easily read resources/prompts or
|
|
when the MCP tool schema is technically correct but easy to misuse.
|
|
|
|
## Boundary
|
|
|
|
`next_actions` is guidance, not authority.
|
|
|
|
- Diagnostics describe machine-readable facts about validity, drift, source
|
|
liveness, blocked resume, and runtime failures.
|
|
- `next_actions` explains the likely next useful tool call.
|
|
- Runtime validation, artifact validation, and dependency validation remain the
|
|
source of truth.
|
|
- `next_actions.can_save_now` is advisory only. It must not block saving.
|
|
|
|
## Proposed Module
|
|
|
|
Create a focused module:
|
|
|
|
```text
|
|
src/wf_mcp/workflow_surface/next_actions.py
|
|
```
|
|
|
|
It should own the reusable result models and constructors. This keeps
|
|
`handlers.py` from accumulating UX policy helpers and keeps `models.py` focused
|
|
on MCP request/response schemas.
|
|
|
|
## Core Types
|
|
|
|
```python
|
|
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
from typing import Any, Self
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class NextActionTool(StrEnum):
|
|
"""Stable MCP workflow tools that guidance may recommend."""
|
|
|
|
PATCH_DRAFT_WORKSPACE = "wf.workflow.patch_draft_workspace"
|
|
VALIDATE_DRAFT_WORKSPACE = "wf.workflow.validate_draft_workspace"
|
|
VALIDATE_DEPLOYMENT = "wf.workflow.validate_deployment"
|
|
RUN_DEPLOYMENT = "wf.workflow.run_deployment"
|
|
RESUME_RUN = "wf.workflow.resume_run"
|
|
READ_RUN_TRACE = "wf.workflow.read_run_trace"
|
|
|
|
|
|
class NextActionPatchExample(BaseModel):
|
|
"""Concrete example request for the recommended tool."""
|
|
|
|
description: str
|
|
tool: NextActionTool
|
|
request: dict[str, Any]
|
|
|
|
|
|
class NextActions(BaseModel):
|
|
"""Advisory continuation hints for MCP workflow clients."""
|
|
|
|
can_continue: bool = Field(
|
|
description=(
|
|
"Whether there is an obvious next workflow-surface tool call. "
|
|
"Advisory only."
|
|
)
|
|
)
|
|
can_save_now: bool | None = Field(
|
|
default=None,
|
|
description=(
|
|
"Advisory wrapper-authoring signal. False means review is "
|
|
"recommended before saving; the server does not enforce it."
|
|
),
|
|
)
|
|
recommended_next_tool: NextActionTool | None = Field(
|
|
default=None,
|
|
description="Suggested next MCP workflow tool, if one is obvious.",
|
|
)
|
|
reason: str
|
|
patch_examples: list[NextActionPatchExample] = Field(default_factory=list)
|
|
warnings: list[str] = Field(default_factory=list)
|
|
```
|
|
|
|
## Constructors
|
|
|
|
Prefer named constructors or classmethods over ad-hoc dict helpers.
|
|
|
|
Initial constructor:
|
|
|
|
```python
|
|
@classmethod
|
|
def from_wrapper_hints(
|
|
cls,
|
|
*,
|
|
workspace_id: str,
|
|
revision: int,
|
|
hints: WrapperAuthoringHints | dict[str, Any],
|
|
) -> Self:
|
|
"""Create guidance after create_draft_workspace_from_capability."""
|
|
```
|
|
|
|
Later constructors:
|
|
|
|
```python
|
|
@classmethod
|
|
def from_deployment_validation(
|
|
cls,
|
|
*,
|
|
deployment_id: str,
|
|
diagnostics: list[DependencyDiagnostic],
|
|
) -> Self:
|
|
"""Create guidance after validate_deployment."""
|
|
|
|
|
|
@classmethod
|
|
def from_run_result(
|
|
cls,
|
|
*,
|
|
run_id: str | None,
|
|
status: str,
|
|
trace_count: int,
|
|
diagnostics: list[DependencyDiagnostic],
|
|
) -> Self:
|
|
"""Create guidance after run_deployment, inspect_run, or resume_run."""
|
|
```
|
|
|
|
## Wrapper Draft Guidance Rules
|
|
|
|
For `create_draft_workspace_from_capability`:
|
|
|
|
- High confidence and no missing decisions:
|
|
- `can_continue=true`
|
|
- `can_save_now=true`
|
|
- `recommended_next_tool=wf.workflow.validate_draft_workspace`
|
|
- Low/medium confidence or any missing decisions:
|
|
- `can_continue=true`
|
|
- `can_save_now=false`
|
|
- `recommended_next_tool=wf.workflow.patch_draft_workspace`
|
|
- include conservative `patch_examples` when the missing decision can be
|
|
represented safely
|
|
|
|
Patch examples must not invent business semantics.
|
|
|
|
Allowed examples:
|
|
|
|
- replace step output bindings with an empty list as a scaffold
|
|
- point the caller to `patch_draft_workspace` with the correct `workspace_id`
|
|
and `revision`
|
|
- include an empty patch for boolean-outcome decisions when no safe automatic
|
|
route exists
|
|
|
|
Not allowed:
|
|
|
|
- auto-extract `content[0].text`
|
|
- auto-route on boolean output fields
|
|
- infer error outcome mapping from arbitrary output data
|
|
|
|
## Future Deployment Guidance
|
|
|
|
For `validate_deployment`:
|
|
|
|
- No diagnostics:
|
|
- recommend `wf.workflow.run_deployment`
|
|
- `source_unreachable`:
|
|
- recommend fixing/reloading source, then `wf.workflow.validate_deployment`
|
|
with `live_check=true`
|
|
- `binding_missing`:
|
|
- recommend `wf.workflow.save_deployment`
|
|
- `capability_missing` or `schema_changed`:
|
|
- recommend inspecting capabilities or refreshing catalog before running
|
|
|
|
The exact deployment constructor can come later. The important rule is that
|
|
diagnostics stay the source of truth; `next_actions` only summarizes.
|
|
|
|
## Future Run Guidance
|
|
|
|
For `run_deployment`, `inspect_run`, and `resume_run`:
|
|
|
|
- `status=completed`:
|
|
- no required next tool
|
|
- optionally suggest `read_run_trace` only if caller is debugging
|
|
- `status=failed`:
|
|
- recommend `inspect_run` or bounded `read_run_trace`
|
|
- `status=interrupted`:
|
|
- recommend `wf.workflow.resume_run`
|
|
- blocked resume:
|
|
- recommend repairing diagnostics and retrying `resume_run`
|
|
|
|
Never recommend reading the full trace. Always point to bounded trace ranges.
|
|
|
|
## JSON Compatibility
|
|
|
|
Existing `create_draft_workspace_from_capability` output should remain stable:
|
|
|
|
```json
|
|
{
|
|
"next_actions": {
|
|
"can_save_now": false,
|
|
"recommended_next_tool": "wf.workflow.patch_draft_workspace",
|
|
"reason": "...",
|
|
"patch_examples": [],
|
|
"warnings": []
|
|
}
|
|
}
|
|
```
|
|
|
|
If the generic model adds `can_continue`, it is additive. Existing clients that
|
|
only read `can_save_now`, `recommended_next_tool`, `reason`, `patch_examples`,
|
|
and `warnings` continue to work.
|
|
|
|
## Migration Plan
|
|
|
|
1. Add `next_actions.py` with generic models and `from_wrapper_hints`.
|
|
2. Re-export or import these models from `workflow_surface.models` if needed for
|
|
FastMCP schema generation.
|
|
3. Replace handler-local `_wrapper_draft_next_actions` helpers with the model
|
|
constructor.
|
|
4. Keep response JSON fields stable.
|
|
5. Add tests proving the old `next_actions` fields still serialize the same.
|
|
6. Later, add deployment/run constructors in separate small passes.
|
|
|
|
## Open Questions
|
|
|
|
- Should `recommended_next_tool` allow non-workflow tools such as
|
|
`wf.admin.reload_config`, or should guidance stay workflow-surface only?
|
|
- Should patch examples grow a typed `tool_request_schema` later, or is raw JSON
|
|
request payload enough?
|
|
- Should `can_continue=false` appear when there is no useful next tool, or should
|
|
`recommended_next_tool=null` be enough?
|