11 KiB
Draft Workspace Lifecycle Authoring Design
Goal
Expose capability-free draft creation and focused workflow metadata edits across
the Python workflow surface, JSON-RPC, and wf CLI. An author must be able to
start with a control, interrupt, end, subgraph, or capability step without first
creating and removing an unrelated capability bootstrap.
Problem
The canonical WorkflowDraft model already represents:
- workflow input, state, and output schemas;
- declared public outcomes;
- a mutable entry point;
- every supported draft step kind.
The transport-facing authoring interface does not expose all of those concepts.
wf draft create requires a capability, while changing start, schemas, or
workflow outcomes requires hand-written RFC 6902 patches. This makes the focused
interface shallower than both WorkflowDraft and WorkflowBuilder.
Draft workspaces already preserve invalid intermediate documents and return structured diagnostics. Capability-free creation can therefore use the existing incremental authoring model rather than inventing a placeholder capability or a second transaction system.
Scope
This slice adds three focused operations:
- create an empty, capability-free draft workspace;
- set the draft entry point;
- replace any supplied workflow contract fields atomically.
Every operation is exposed through:
WorkflowApiandWorkflowDraftSurface;- Python JSON-RPC server models and methods;
RpcWorkflowApiClient;- local and remote
wfCLI handlers; - user-facing draft documentation and agent skills.
The MCP tool frontend remains unchanged in this slice. CLI-using agents gain the new lifecycle operations through the local or remote workflow surface; native MCP tool parity is a separate follow-up.
Out Of Scope
This slice does not:
- add deep-merge semantics for JSON Schema;
- add field-selection or schema projection from capability contracts;
- replace focused path maps with canonical binding-list editors;
- add literal binding commands;
- add a general multi-operation authoring transaction;
- add or change MCP workflow tools, request models, or proxy tool inventories;
- expand the TypeScript Effect RPC operation catalog;
- add focused updates for step descriptions, retries, or timeouts;
- fix revision precedence in unrelated semantic edit helpers.
Those remain separate issues. Existing capability-aware schema projection stays available and complements the explicit whole-contract operation.
Interface
Empty Workspace Creation
Add the transport-facing operation:
async def create_empty_draft_workspace(
*,
workspace_id: str,
name: str,
title: str | None = None,
input_schema: dict[str, Any] | None = None,
state_schema: dict[str, Any] | None = None,
output_schema: dict[str, Any] | None = None,
outcomes: Sequence[str] = ("ok",),
) -> dict[str, Any]: ...
Omitted schemas become independent empty object schemas:
{"type": "object", "properties": {}}
The operation stores this stable revision-1 draft shape:
{
"name": "report_workflow",
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
"outcomes": ["ok"],
"output": [],
"start": "",
"steps": {},
"routes": {}
}
The empty entry point intentionally makes the workspace invalid. Creation still succeeds, returns revision 1, and includes the existing structured validation diagnostic. No placeholder node or capability is created.
Duplicate workspace IDs use the existing workspace_exists conflict result and
do not overwrite the stored workspace.
Entry Point Edit
Add:
async def set_draft_start(
*,
workspace_id: str,
revision: int,
step_id: str,
) -> dict[str, Any]: ...
The operation replaces /start in one revision. step_id must be non-empty,
but it may refer to a step that has not been added yet. A forward reference is
persisted and reported through normal invalid-draft diagnostics, matching the
existing incremental behavior of forward route targets.
Workflow Contract Edit
Add:
async def set_draft_contract(
*,
workspace_id: str,
revision: int,
input_schema: dict[str, Any] | None = None,
state_schema: dict[str, Any] | None = None,
output_schema: dict[str, Any] | None = None,
outcomes: Sequence[str] | None = None,
) -> dict[str, Any]: ...
The operation replaces each supplied top-level field and preserves every omitted field. All supplied replacements are applied by one JSON Patch call and produce exactly one new workspace revision.
Schema documents are whole-field replacements, not deep merges. JSON Schema
keywords such as required, $defs, unions, and nested properties do not
have one safe generic merge rule. Callers that need field-level surgery retain
the existing raw JSON Patch operation. State reducer declarations remain schema
metadata and are preserved when supplied in a replacement state_schema; this
slice does not invent a separate reducer-edit model.
At least one contract field must be supplied. Supplied outcomes must contain at least one non-empty, unique value. Invalid request envelopes fail before mutation; structurally inconsistent but representable draft contracts are persisted and reported through normal draft diagnostics.
CLI
Broaden the existing command without breaking capability-backed callers:
# Capability-free creation
wf draft create report_ws --name report_workflow
# Existing behavior remains valid
wf draft create report_ws --capability docs.local.read_documents
Rules:
--capabilitybecomes optional.- Without
--capability,--nameis required. - With
--capability, the current capability-bootstrap path and defaults stay unchanged. - Optional schema files and repeated outcomes belong to capability-free creation only. The existing capability bootstrap remains the contract-derived convenience path.
Add focused commands:
wf draft set-start report_ws --revision 2 --step review
wf draft set-contract report_ws --revision 3 \
--input-schema-file input.schema.json \
--state-schema-file state.schema.json \
--output-schema-file output.schema.json \
--outcome ok \
--outcome error
Each schema file must decode to a JSON object. Repeated --outcome flags replace
the complete outcomes list. Calling set-contract without any contract option
is a concise CLI input error and does not contact the server.
JSON-RPC
Register distinct methods rather than overloading the existing capability request model:
workflow.draft_workspaces.create_empty
workflow.draft_workspaces.set_start
workflow.draft_workspaces.set_contract
Distinct request models keep method discovery and generated schemas explicit.
The remote client implements the same WorkflowDraftSurface interface as the
local WorkflowApi, so CLI behavior remains target-independent.
Revision And Error Semantics
set_draft_start and set_draft_contract delegate mutation to the canonical
revisioned workspace patch operation. Therefore:
- stale revisions return
revision_conflict; - stale requests do not mutate the workspace;
- after a request envelope is valid, revision conflict is checked before validation that depends on current workspace content;
- forward references and other representable invalid states consume one revision and return diagnostics;
- replacing a field with an equal value still consumes one revision, matching ordinary JSON Patch behavior in this slice.
Request-envelope errors such as no supplied contract field, an empty outcome list, duplicate outcomes, or a non-object schema are rejected before workspace lookup and therefore may precede a stale-revision result. They never mutate the workspace.
Testing
Tests must cover each interface rather than only the lowest-level helper.
Python API
- capability-free creation stores the exact stable skeleton;
- default schema objects are independent values;
- custom schemas, outcomes, name, and title are preserved;
- reducer metadata in a custom state schema is preserved unchanged;
- duplicate workspace IDs return
workspace_exists; - setting an existing start step can make a draft valid;
- setting a missing start step persists an invalid forward reference;
- a multi-field contract edit uses one revision;
- omitted contract fields remain byte-for-byte unchanged;
- stale start and contract edits return
revision_conflictwithout mutation; - empty or duplicate outcomes and empty contract edits do not mutate.
JSON-RPC And Remote Client
- all three method names are registered;
- request models enforce field and outcome constraints;
- server methods call the workflow surface with exact arguments;
- the remote client emits the expected JSON-RPC params and decodes workspace results;
- remote conflict and invalid-draft results preserve the existing envelope.
- one remote JSON-RPC test executes the complete capability-free lifecycle, rather than testing only isolated method dispatch.
CLI
wf draft create --nameuses capability-free creation locally and remotely;- omitting both
--capabilityand--nameis rejected; - existing
wf draft create --capabilitytests remain green; - schema files and outcomes are parsed exactly once;
set-startandset-contractdispatch identical local and remote calls;- empty contract invocation, non-object schema files, and duplicate outcomes produce compact parameter errors.
End-To-End Authoring
Exercise this sequence once through the local WorkflowApi and once through
the remote JSON-RPC client:
- create an empty workspace;
- add a non-capability first step, such as
interruptorwhen; - set that step as the entry point;
- add an
endstep with a non-okoutcome; - declare that outcome through
set_draft_contract; - validate successfully without raw JSON Patch.
Documentation
Update:
docs/workflow_drafts.mdwith the capability-free lifecycle;skills/wf-cli/SKILL.mdandskills/wf-workflow/SKILL.mdwith the new commands and when to use contract replacement versus schema projection;docs/current_roadmap.mdwith the completed implementation link after the code slice lands;ISSUES.mdby checking the capability-free creation, start, outcomes, and schema-editing items only after their public surfaces and tests exist.
Success Criteria
- A caller can create a draft workspace without naming a capability.
- The empty workspace has stable patch targets and honest invalid diagnostics.
- A caller can set a forward-referenced entry point through a focused command.
- A caller can replace any combination of workflow schemas and outcomes in one revision without deep-merge ambiguity.
- Local API, JSON-RPC, remote client, and CLI expose equivalent behavior.
- Existing capability-backed draft creation remains compatible.
- A control-first workflow can reach valid status without raw JSON Patch.