node output vs workflow output clarification
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user