docs: design generic draft step authoring
This commit is contained in:
@@ -0,0 +1,215 @@
|
|||||||
|
# Generic Draft Step Authoring Design
|
||||||
|
|
||||||
|
**Status:** Proposed
|
||||||
|
|
||||||
|
**Date:** 2026-07-20
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Draft authoring has a focused helper for capability-backed `node` steps, but
|
||||||
|
the other canonical workflow step kinds have no equivalent application or RPC
|
||||||
|
operation. Callers can patch raw draft JSON, but that bypasses the product's
|
||||||
|
typed authoring vocabulary and forces agents to hand-build JSON Patch paths.
|
||||||
|
|
||||||
|
The affected canonical step kinds are:
|
||||||
|
|
||||||
|
- `subgraph`
|
||||||
|
- `condition`
|
||||||
|
- `foreach`
|
||||||
|
- `join`
|
||||||
|
- `end`
|
||||||
|
- `interrupt`
|
||||||
|
|
||||||
|
The CLI also exposes only the flat `wf draft add-step --capability ...`
|
||||||
|
command. Extending that command with a `--type` switch would create one large
|
||||||
|
conditional form whose required and valid options change by step kind.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Add one generic, typed Python application operation for inserting any
|
||||||
|
canonical `Step` into a draft workspace.
|
||||||
|
- Expose the operation through Python JSON-RPC and the Python RPC client.
|
||||||
|
- Replace the flat capability-only CLI command with a discoverable
|
||||||
|
`wf draft add` command group.
|
||||||
|
- Preserve the capability helper's composed schema projection and binding
|
||||||
|
behavior under `wf draft add capability`.
|
||||||
|
- Make each control-step command validate only the options relevant to that
|
||||||
|
step kind.
|
||||||
|
- Keep one optimistic revision increment for the inserted step and any route
|
||||||
|
wiring requested in the same command.
|
||||||
|
- Record implementation discoveries in `ISSUES.md`: strike resolved issues and
|
||||||
|
add concrete bugs or missing product behavior found during the work.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- TypeScript or Effect RPC parity.
|
||||||
|
- RPC code generation.
|
||||||
|
- Web workflow authoring UI.
|
||||||
|
- New workflow step kinds or runtime semantics.
|
||||||
|
- Compatibility aliases for unused command shapes.
|
||||||
|
- Replacing the existing capability-composition helper with a raw node insert.
|
||||||
|
|
||||||
|
## Application API
|
||||||
|
|
||||||
|
Add a generic operation to the workflow API surface:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def add_step(
|
||||||
|
*,
|
||||||
|
workspace_id: str,
|
||||||
|
revision: int,
|
||||||
|
step: Step,
|
||||||
|
route_from_step: str | None = None,
|
||||||
|
route_from_outcome: str = "ok",
|
||||||
|
routes: dict[str, str] | None = None,
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`step.id` is the canonical identifier. The operation does not accept a second
|
||||||
|
`step_id` that could disagree with the model.
|
||||||
|
|
||||||
|
The operation:
|
||||||
|
|
||||||
|
1. parses and validates the discriminated `Step` union before mutation;
|
||||||
|
2. rejects an existing step id;
|
||||||
|
3. inserts the canonical serialized step into the draft's `steps` object;
|
||||||
|
4. optionally routes one existing step outcome into the new step;
|
||||||
|
5. optionally records outgoing routes supplied for the new step; and
|
||||||
|
6. applies the complete change through one revision-checked draft patch.
|
||||||
|
|
||||||
|
Draft workspaces intentionally support invalid intermediate states. Therefore,
|
||||||
|
`add_step` does not require every declared outcome to be routed immediately.
|
||||||
|
When routes are supplied, it rejects outcomes that the inserted step cannot
|
||||||
|
emit. End steps reject outgoing routes. Full graph completeness remains the
|
||||||
|
responsibility of draft validation.
|
||||||
|
|
||||||
|
The existing capability-composition operation remains a distinct application
|
||||||
|
helper because it resolves a capability, projects schemas, constructs a
|
||||||
|
`NodeUse`, and creates bindings. It may reuse the generic insertion mechanics
|
||||||
|
internally, but its public behavior must not regress.
|
||||||
|
|
||||||
|
## JSON-RPC And Python Client
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```text
|
||||||
|
workflow.draft_workspaces.add_step
|
||||||
|
```
|
||||||
|
|
||||||
|
Its parameter model mirrors the application operation. The `step` field uses
|
||||||
|
the canonical discriminated `Step` union rather than an unvalidated
|
||||||
|
`dict[str, Any]`. RPC errors continue through the existing
|
||||||
|
`WorkflowRpcError` translation boundary.
|
||||||
|
|
||||||
|
The Python RPC client implements the same method on the workflow API surface.
|
||||||
|
Round-trip tests cover every step variant so the transport cannot silently
|
||||||
|
drop aliases, schemas, policies, bindings, outcomes, or workflow references.
|
||||||
|
|
||||||
|
## CLI Shape
|
||||||
|
|
||||||
|
Create a Typer subgroup beneath `wf draft`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wf draft add capability
|
||||||
|
wf draft add interrupt
|
||||||
|
wf draft add condition
|
||||||
|
wf draft add foreach
|
||||||
|
wf draft add join
|
||||||
|
wf draft add end
|
||||||
|
wf draft add subgraph
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing `wf draft add-step` command is removed rather than retained as a
|
||||||
|
ghost alias. Repository-owned docs, tests, skills, examples, and scripts are
|
||||||
|
migrated to `wf draft add capability`.
|
||||||
|
|
||||||
|
Every command shares these routing options where meaningful:
|
||||||
|
|
||||||
|
- workspace id argument;
|
||||||
|
- `--revision`;
|
||||||
|
- `--step`;
|
||||||
|
- optional `--from-step` and `--from-outcome`; and
|
||||||
|
- repeatable `--route OUTCOME=TARGET` for step kinds with outgoing outcomes.
|
||||||
|
|
||||||
|
The commands then expose only their own model fields:
|
||||||
|
|
||||||
|
- `capability`: capability name plus existing input and output binding flags;
|
||||||
|
- `interrupt`: kind, request/resume schema files, request/resume bindings, and
|
||||||
|
repeatable outcomes;
|
||||||
|
- `condition`: a JSON condition document;
|
||||||
|
- `foreach`: source path, item context name, serial/concurrent mode, item-error
|
||||||
|
policy, and concurrent limits;
|
||||||
|
- `join`: no additional step fields;
|
||||||
|
- `end`: workflow outcome and no outgoing routes; and
|
||||||
|
- `subgraph`: workflow reference, boundary schema files, bindings, and
|
||||||
|
repeatable outcomes.
|
||||||
|
|
||||||
|
Compound model values use JSON files rather than dense inline JSON. Existing
|
||||||
|
map-style flags are reused for simple path bindings when their direction is
|
||||||
|
unambiguous. CLI help includes one valid example per command and directs users
|
||||||
|
to `wf draft validate` after editing.
|
||||||
|
|
||||||
|
## Validation And Errors
|
||||||
|
|
||||||
|
- Pydantic owns step-shape validation; CLI and RPC do not duplicate the core
|
||||||
|
model rules.
|
||||||
|
- CLI parsing errors identify the invalid flag or file before making an API
|
||||||
|
call.
|
||||||
|
- Application errors identify duplicate ids, missing incoming source steps,
|
||||||
|
unsupported route outcomes, and forbidden end-step routes.
|
||||||
|
- Revision conflicts preserve the existing draft-workspace behavior.
|
||||||
|
- No command guesses missing routes or silently invents bindings.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### Application
|
||||||
|
|
||||||
|
- Parameterized insertion for every `Step` variant.
|
||||||
|
- Atomic incoming and outgoing route wiring.
|
||||||
|
- Duplicate id rejection without mutation.
|
||||||
|
- Unknown outcome and end-route rejection without mutation.
|
||||||
|
- Invalid intermediate drafts remain persistable and validate diagnostically.
|
||||||
|
|
||||||
|
### RPC And Client
|
||||||
|
|
||||||
|
- Parameter model rejects malformed discriminators and variant fields.
|
||||||
|
- App round trip for every step kind.
|
||||||
|
- Client method emits the exact method name and canonical payload.
|
||||||
|
- RPC failures occur before draft mutation.
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- The `wf draft add` help lists all seven commands.
|
||||||
|
- Per-command help exposes only relevant options.
|
||||||
|
- Each command constructs the expected canonical step and route payload.
|
||||||
|
- `add capability` preserves existing composed authoring behavior.
|
||||||
|
- Removed `add-step` references are absent from live docs and tests.
|
||||||
|
|
||||||
|
## Documentation And Issue Tracking
|
||||||
|
|
||||||
|
- Update CLI docs, agent skills, examples, and roadmap references to the new
|
||||||
|
command shape.
|
||||||
|
- Mark the dedicated-step-authoring issue in `ISSUES.md` resolved when all six
|
||||||
|
non-capability commands are covered.
|
||||||
|
- Add newly discovered defects to `ISSUES.md` only when they are concrete,
|
||||||
|
reproducible, and outside this slice. Fix in-scope defects instead of merely
|
||||||
|
documenting them.
|
||||||
|
|
||||||
|
## Deferred Work
|
||||||
|
|
||||||
|
A later parity slice may expose the full Python JSON-RPC suite through the
|
||||||
|
TypeScript Effect RPC package. That work should first add a machine-checked
|
||||||
|
method parity manifest. Whether schemas are generated should be decided from
|
||||||
|
the canonical Python registry and schema-export capabilities, not by generating
|
||||||
|
from duplicate handwritten TypeScript definitions.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Every canonical workflow step kind can be added through the application API,
|
||||||
|
Python JSON-RPC, Python RPC client, and a type-specific CLI command.
|
||||||
|
- One generic `add_step` operation owns raw typed insertion.
|
||||||
|
- Capability-backed insertion retains schema projection and binding behavior.
|
||||||
|
- CLI vocabulary is grouped under `wf draft add` with no unneeded compatibility
|
||||||
|
alias.
|
||||||
|
- Invalid requests fail before mutation and revision semantics remain atomic.
|
||||||
|
- Focused tests, type checking, formatting, and documentation checks pass.
|
||||||
Reference in New Issue
Block a user