# Draft Semantic Authoring Boundary Date: 2026-06-27 Status: Approved for implementation planning. Canonical Path Strings section implemented. Related: - [Workflow drafts](../../workflow_drafts.md) - [`wf_authoring` control flow](../../wf_authoring_control_flow.md) - [Workflow API architecture](../../wf_api_architecture.md) - [Current roadmap](../../current_roadmap.md) ## Purpose Define a stable boundary between the persisted draft document, revisioned draft workspaces, and capability-aware authoring operations intended for agents. The draft system must remain one authoring model rather than becoming a second workflow language beside `WorkflowBuilder`. Semantic operations therefore use the same control-flow vocabulary as `WorkflowBuilder`, but lower each intent to one atomic patch against the persisted `WorkflowDraft` representation. ## Current Problem The current `WorkflowDraftApi` owns several distinct responsibilities: - draft parsing, validation, and compilation; - revisioned workspace lifecycle and JSON Patch application; - low-level focused document edits; - capability lookup and schema projection; - compound authoring operations such as adding a capability step. The responsibilities are individually valid, but keeping them in one service obscures the boundary and allows semantic behavior to diverge from `WorkflowBuilder`. The existing `add_step_from_capability` demonstrates this: it writes one outgoing route even when the capability declares multiple outcomes, producing a draft that immediately fails validation. ## Core Decisions ### One Persisted Authoring Representation `WorkflowDraft` remains the only persisted authoring representation. It is a patch-friendly intermediate form with keyed `steps`, `routes`, schemas, and explicit bindings. `RawWorkflowPlan` remains the normalized execution model. ```text semantic authoring intent -> atomic WorkflowDraft patch -> revision check -> compile and validate -> persisted WorkflowDraft workspace -> RawWorkflowPlan projection ``` Semantic operations do not persist builder objects, create an additional graph model, or modify raw plans in place. ### Separate Semantic Authoring Service Introduce `WorkflowDraftAuthoringApi` as a sibling of `WorkflowDraftApi`. `WorkflowDraftApi` owns: - workspace create, get, list, delete, and revision handling; - draft validation and compilation; - raw JSON Patch; - low-level focused document edits; - read-only projection of a stored workspace to `RawWorkflowPlan`. `WorkflowDraftAuthoringApi` owns: - capability-aware draft bootstrap; - semantic construction of draft step kinds; - adding a capability-backed `use` step; - projecting and binding capability outputs into state; - `branch` and `handle` semantic routing operations. `WorkflowDraftAuthoringApi` depends on `WorkflowDraftApi` for workspace access and patch application. It must not write the workspace store directly. Every semantic operation produces one patch and consumes one revision. `WorkflowApi` continues to expose one protocol-neutral facade. RPC, MCP, and CLI clients do not need to know about the internal service split. The service boundary is intentionally not capability-only. The current draft model also represents `end`, `condition`, `interrupt`, `foreach`, `when`, `choose`, `match`, and subgraph steps, and core may gain more step kinds. This slice adds semantic operations only where required, but new step-kind helpers belong in `WorkflowDraftAuthoringApi` rather than a parallel authoring system. ### Match `WorkflowBuilder` Vocabulary The semantic draft surface uses the established authoring terms: - `branch`: connect several outcomes from one existing step to targets; - `handle`: connect several source-step/outcome pairs to one shared target. These operations add or replace edges only. They do not create condition steps, wait for concurrent branches, or implement join semantics. `match`, `when`, and `choose` remain outside this first slice. ## Public Operation Levels The public draft surface is documented in descending order of preference. ### Semantic Authoring Operations - `create` - `add-step` - `bind` - `branch` - `handle` These operations understand capability definitions, schemas, outcomes, or graph intent. They are the preferred agent authoring surface. ### Low-Level Focused Edits - `set-name` - `set-route` - `set-input` - `set-output` These remain available for precise repairs. `set-input` and `set-output` do not project workflow schemas; callers should prefer `bind` when a capability input/output should also declare the matching workflow input, state, or output schema. ### Escape Hatch `patch` remains the RFC 6902 escape hatch for structural edits that semantic or focused operations do not cover. ### Lifecycle And Projection - `list` - `inspect` - `validate` - `compile` - `save` - `delete` These operations manage or inspect the workspace rather than expressing graph authoring intent. ## Operation Contracts ### Add Step From Capability `add capability` atomically adds: - one capability-backed `use` step; - explicit input bindings; - output-to-state bindings and required state schema projection; - an optional incoming route; - the complete outgoing route map. The outgoing CLI option is repeatable: ```powershell wf draft add capability WORKSPACE ` --revision 4 ` --step second_echo ` --capability everything.default.echo ` --route ok=next ` --route error=tool_error ``` When the caller supplies no routes and the capability declares exactly one outcome, the operation infers that outcome, regardless of its name, and routes it to `__end__`. If capability metadata declares no outcomes, the inferred outcome is `ok`. When a capability has multiple known outcomes, the caller must provide a target for every outcome. Missing or unknown outcomes reject the operation before mutation and report the declared outcomes. Callers can always override the target by supplying an explicit route. The current singular `route_outcome` and `route_to` shape has no known external caller or persisted-data dependency and is replaced rather than retained as ghost compatibility behavior. ### Branch `branch` applies several outcome routes from one existing step in one revision: ```powershell wf draft branch WORKSPACE --revision 5 --step classify ` --route send=send_email ` --route skip=__end__ ` --route error=tool_error ``` Supplied outcomes add or replace their route. Routes for outcomes omitted from the request remain unchanged. The operation rejects an empty route map, unknown source step, unknown declared outcome, or malformed target before mutation. Normal workflow validation remains responsible for missing required outcomes, unknown target steps, and broader graph consistency. ### Handle `handle` redirects several source-step/outcome pairs to one shared target: ```powershell wf draft handle WORKSPACE --revision 6 --to tool_error ` --branch lookup:error ` --branch transform:error ``` The transport request uses structured pairs rather than encoded strings. The CLI parses each `STEP:OUTCOME` value at the final colon and rejects malformed values before making the request. Existing routes unrelated to the supplied pairs remain unchanged. `handle` is not a join. It creates ordinary directed edges to one target. ### Bind `bind` is the capability-aware schema propagation operation. It projects the selected capability local input/output property and required `$defs` into the workflow input, state, or output schema, then merges the matching step input or output binding in the same revision. ```powershell wf draft bind WORKSPACE --revision 4 --step wait ` --from input.simulate ` --to local.simulate wf draft bind WORKSPACE --revision 5 --step wait ` --from local.after ` --to state.after ``` The partial `add-state-from-output` operation is removed from API, RPC, MCP, CLI, docs, and skills. It was superseded before acquiring a real caller or persisted-data contract. ### Compile A Stored Workspace Add a read-only workspace projection: ```powershell wf draft compile WORKSPACE ``` The server operation: 1. reads the stored workspace; 2. validates it in memory against current capability definitions; 3. compiles it through the existing draft adapter; 4. returns `compiled_plan` and required capability metadata. The CLI prints only the bare `compiled_plan` JSON so it can be inspected or piped directly into another command. The operation does not save an artifact, refresh stored diagnostics, increment the revision, or otherwise mutate the workspace. An invalid workspace returns structured diagnostics and a nonzero CLI exit. It must not emit a partial raw plan. ## Validation And Error Behavior All semantic mutations use the current workspace revision. A stale revision, malformed request, unknown capability, or semantic precondition failure leaves the workspace unchanged. Once a semantic patch is constructed, it passes through the existing workspace patch path. That path performs the revision check, draft parsing, compilation, structural validation, persistence, and refreshed diagnostics. Known request-local mistakes should fail before mutation with specific data: - multi-outcome step missing routes: include missing and declared outcomes; - branch with unknown outcome: include the step's declared outcomes; - handle with unknown source step: identify the missing step; - duplicate route or branch values in one CLI invocation: reject as ambiguous; - invalid compile: return the same structured diagnostic vocabulary as draft validation. Draft workspaces may remain invalid during iterative low-level editing. Semantic operations should avoid creating a known-invalid result when all required information is already available in the request and capability catalog. ## Transport Shape The protocol-neutral API uses mappings and structured records: - branch routes: `dict[str, str]` mapping outcome to target; - handle branches: a list of `{step_id, outcome}` records plus one target; - add-step routes: `dict[str, str]` mapping every declared outcome to target. RPC request models and MCP request models mirror those shapes. CLI parsing is a front-end concern and must not leak encoded `STEP:OUTCOME` strings into the application API. ## Canonical Path Strings Authoring surfaces use one canonical TOML-key path grammar. Examples include: ```text state.report.title input."customer.name" local.items ``` The underlying `GraphSourcePath`, `StatePath`, and `LocalPath` models remain structured typed values. Strings are the public and serialized representation; the models parse those strings once at their boundary. Move TOML-key parsing from the `wf_authoring` convenience layer into `wf_core` so CLI, RPC, MCP, drafts, raw plans, and Python authoring use the same parser and formatter. The shared grammar must support quoted TOML keys for literal dots, spaces, and other non-bare segments. Parse errors identify the complete input and recommend quoting the invalid segment. Pydantic JSON schemas advertise both canonical path strings and structural `{root, parts}` objects. Serializers emit canonical strings. Validators continue to accept structural objects so machine clients and old persisted drafts, artifacts, and runs can use the explicit root/parts form; new public examples and writes prefer strings. ## Compatibility And Migration No workflow semantics or draft field layout changes. The serialized path representation changes from structural objects to canonical strings. Existing workspaces, artifacts, deployments, and runs remain readable: their path objects are accepted on input and become canonical strings when a containing record is rewritten. Low-level operations remain available. The migration changes only compound operation signatures and removes the unused partial schema helper. Repository callers, tests, docs, and skills are updated in the same slice. No compatibility shim is added without a real external caller. ## Testing Strategy ### Domain And Service Tests - branch merges supplied routes and preserves unrelated routes; - handle updates several source routes atomically; - malformed requests and stale revisions do not mutate the workspace; - multi-outcome add-step requires complete routes; - route inference chooses the sole declared outcome even when it is not `ok`; - absent outcome metadata falls back to `ok`; - an inferred route targets `__end__` unless explicitly overridden; - output binding still projects referenced schema definitions; - all path models parse and serialize the canonical TOML-key string grammar; - structural path objects remain accepted input but are not emitted; - stored-workspace compile equals `compile_workflow_draft` output; - compile does not change revision, timestamps, status, or diagnostics. ### Surface Tests - RPC and client methods preserve structured route data; - MCP tools expose branch, handle, and workspace compile; - CLI repeatable options parse into the protocol-neutral request shape; - CLI compile prints only the raw plan and exits nonzero for invalid drafts; - help text distinguishes semantic operations, low-level edits, and JSON Patch. ### Integration Regression Build a two-step workflow where the second capability declares `ok` and `error`. Add it with complete routes, save the artifact and deployment, run it, and verify both steps execute without requiring a follow-up `set-route` repair. ## Non-Goals - adding `match`, `when`, or `choose` draft commands in this slice; - changing workflow semantics or the `WorkflowDraft` field layout; - replacing JSON Patch; - treating `handle` as synchronization or join behavior; - automatic semantic compatibility analysis between connected schemas; - saving artifacts as a side effect of compile.