RFC: Task as One Document
Make "form" and "task" one declarative substrate: containers and elements from a single registry, one conditional grammar for both form visibility and step branching, and a step reduced to a role a container plays rather than a separate table.
💬 Feedback wanted — this is a direction, not a committed plan. It builds on the shipped Form Builder Architecture. Weigh in on the GitHub Discussion before this graduates from draft. Open questions are flagged in Risks & open questions.
This is a horizon, not delivery scope. The form-builder foundation is buildable and shipping today; this RFC is the end state that work deliberately ladders toward. It exists to give direction a stable home, not to schedule a project. The insurance is already taken: the envelope reserves the facets key, so nothing here forecloses.
TL;DR
Two registries, two grammars — for one thing
Most task step types are form elements shoved into the step registry by an old migration. Steps and elements already overlap; only a table boundary keeps them apart.
A step is a container wearing a facet
Assignment, completion, and status are a facet any container can wear — not a separate kind of thing. The onboarding granularity survives; the artifact retires.
One AI document, ORTaskStep still underneath
At the decisive rung the agent authors a whole task in one envelope, projecting onto today's step rows on write. The physical table collapse is a separate, optional bet.
Motivation
The two-level structure — a task is a tree of steps; one step type is FORM, which is itself a tree of elements — is partly historical. Around two-thirds of task step types are FORM_* types: form elements that a migration pushed into the step-type registry. The step registry and the element registry already overlap, messily, separated only by the table boundary.
Strip a step down to what it has that a form element does not:
Who must complete this. Almost always inherited from the task; set explicitly only in narrow, system-controlled cases.
Who actually answered, when, and what state the step is in — genuine audit/attribution data, orthogonal to who it was assigned to.
A string-match edge in a separate join table, with its own branching grammar distinct from the form's visibility conditions.
Everything else about a step is already "a form element with a type." So a step ≈ a form element carrying an assignment facet, a completion facet, and a branch. Two things follow, and they pull in opposite directions:
- Real domain — keep it. Onboarding is inherently multi-party ("customer uploads → CSM reviews → customer signs"): distinct owners, due dates, reminders, dashboard counting. The assignment/completion granularity is the product, not a rendering detail.
- Artifact — retire it. That steps are a separate kind of thing, in a separate table, with a separate branching grammar and a separate registry is history. The string-match branching edge especially is pure artifact.
The goal is never "kill steps." It is kill the duplication, keep the granularity — by making {assignment, completion, status} a facet a container wears, not a table.
Proposal
A task is a single declarative document. "Form" and "task" stop being two things — they are one substrate. Containers and elements come from one registry. Some containers wear an assignment + completion + status facet — that is all a "step" degrades to: a role a container plays. Every conditional — visibility, branching, required, calculated value — runs on one engine. The agent authors the entire task in one envelope and one grammar, not "a form doc plus step rows plus edge conditions."
The existing columns already behave like facets, which is why they collapse cleanly: the assignee is null in the vast majority of steps (null ⇒ inherit the task's assignee — derivation, not storage) and is system-set only for generated signer steps; completion attribution is generic and often records an internal user answering in a customer's stead. Both map onto a default-plus-override container facet with no loss.
Two guardrails keep the design honest:
- Don't overshoot to "pure single form, no steps at all." That only works if onboarding were single-submitter. It isn't. Flattening assignment away loses per-owner completion, notifications, and analytics. Granularity must survive as a facet.
- Don't undershoot to "just harden the form builder." Two registries and two grammars forever leaves the duplication and the two-contract AI story intact. The convergence is the point.
The ladder
Climbed strangler-style. The document-as-contract-over-rows banks most of the value before touching storage or the external answer contract.
Converge the registries. Fold the step types into the one container/element plugin registry the form builder already uses. Cheap, high-leverage — the step registry is mostly form elements already.
flowchart LR
R1["1 · One registry"] --> R2["2 · One engine"]
R2 --> R3["3 · One envelope<br/>(projects onto ORTaskStep)"]
R3 -.->|the gate| R4["4 · Facet runtime<br/>(multi-party completion)"]
R4 -.-> R5["5 · Physical collapse<br/>(external contract migration)"]
style R3 fill:#e6f7e6,color:#000
style R4 fill:#fff3e0,color:#000
style R5 fill:#f3e6ff,color:#000Feasibility verdict. Rungs 1–3 are highly feasible — a natural upward extension of the form-builder foundation. At rung 3 there is already one grammar, one engine, one AI document, with the step row surviving underneath as a projection carrying the facets. Rungs 4–5 are feasible but gated on hard runtime and external-contract work, and are not required to realize the value. The answer to "could we live in a world with only the form?" is yes, at the level that matters (rung 3). Whether the row ever physically disappears is a separate, deferrable bet.
The decisive next step
Everything hinges on one hypothesis: can one registry model "an assignable, independently-completable, branchable container" as a facet of a form element? The cheap, decisive probe is a schema spike — model a step as container + {assignment, completion, status} facets in the envelope and test it against the step-only behaviors (per-assignee routing including generated signer steps, proxy completion, status, branching). If those facets ride cleanly, the north star is proven at the schema level and rungs 1–3 become a funded plan. This must happen before the form-builder envelope's shape freezes, so it is designed to receive steps — which is exactly why the facets key is already reserved in the envelope today.
Alternatives considered
- Pure single form, no steps. Rejected — it assumes single-submitter onboarding and discards real per-owner completion, notifications, and analytics.
- Leave the two registries and grammars as they are. Rejected — it locks in the duplication and forces the AI to author against two contracts (a form doc, step rows, and edge conditions) forever.
- Physically collapse the step table now. Rejected as a starting move — it is the heaviest, gated rung and carries an external-contract migration; the value is banked three rungs earlier without it.
Risks & open questions
Multi-party / partial completion of one document is real runtime work, entangled with the deferred e-sign and multi-signer design.
Mitigation: it is gated behind rung 3, and rungs 1–3 deliver the value without it. The step row persists as a projection until this clears.
Assignee and completion are columns today — indexable, joinable ("all steps assigned to user X", "signatures user Y still owes"). Moving them into a JSON facet trades that away unless projected out.
Mitigation: a read-model projection over the facets — the same pattern as the optional answer read-model — restores the views without making the facet source-of-truth relational.
Open questions
Must the facet ship with a projection from day one? If assignment/completion queryability is important enough, the read-model becomes non-optional infra rather than a lazy add — this is the deciding factor in whether physical collapse is ever worth it.
Where does branch-edge unification land in the sequence? Retiring the string-match branching grammar in favor of filterSet edges is rung 2, but the runtime must keep supporting existing branching data throughout.
Related
- Form Builder Architecture — the shipped foundation this extends upward (the envelope, the engine, the reserved
facetskey). - Form Builder Feature Landscape — the product roadmap the same foundation unlocks.