Skip to content

Commit 3bd7f54

Browse files
committed
docs(#15705): document headless screen satisfaction + flow input params
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
1 parent 24f50bb commit 3bd7f54

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@objectstack/service-automation": minor
3+
"@objectstack/runtime": minor
4+
---
5+
6+
A screen flow can now be completed by a headless caller, and `list_actions` publishes its input names.
7+
8+
An `ai.exposed` action whose target is a **screen flow** could be started over MCP and never finished. `run_action` seeded the flow's `isInput` variables from the caller's `params` — correctly — and the screen node suspended anyway, because the only inputs to that decision were "does the node declare fields" and the author's `waitForInput` flag. The MCP tool set has no verb to resume a parked run, so `ai.exposed` meant "the agent can invoke this", not "the agent can complete this". The fallback an agent took instead — re-implementing the flow's tail with `create_record` + `update_record` — bypasses whatever business rules the flow encapsulated.
9+
10+
Two independent halves:
11+
12+
- **A screen the caller already answered no longer pauses.** When the caller named at least one of the screen's own fields and every `required` one has a value from that caller, there is nothing left to collect and the run continues. Optional fields may come from anywhere (including a declared `defaultValue`).
13+
- **`list_actions` publishes a flow action's inputs.** A `type: 'flow'` action's contract is its target flow's `isInput` variables, not `action.params`; those are now surfaced in declaration order with the `label`, `type`, `required` and select `options` of the screen field that collects each one. An action that declares its own `params[]` keeps them — the flow is read only where the action declared nothing.
14+
15+
**Interactive runs are unchanged.** A console run supplies none of the screen's fields, so it renders the form exactly as before — including when the subject record carries a column named like one of the screen's fields, which is the trigger record speaking rather than the caller. Two screens never take the new path, because they declare nothing to satisfy and must not be answered vacuously: a message-only screen (no fields), and any screen whose author wrote `waitForInput: true`. `waitForInput: false` remains the wrong tool for the headless case — it skips the form for interactive users too.
16+
17+
⚠️ This does **not** make every screen flow completable over MCP. A call that omits the inputs still parks, and nothing on that surface can resume it; that half is a resume verb and is not this change.

content/docs/automation/flows.mdx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,57 @@ producer), and `'service'` when the decision to continue belongs to your service
734734
rather than to whoever holds the run id. Declaring neither leaves your pauses
735735
refused on the generic route, and the engine warns about it at registration.
736736

737+
### A screen the caller already answered does not pause
738+
739+
A `screen` node exists to collect input. When the run's **caller already supplied
740+
it**, there is nothing left to collect, so the run continues past the screen
741+
instead of suspending.
742+
743+
That is what makes a screen flow reachable from a headless invoker. An
744+
`ai.exposed` action whose target is a screen flow is started with `params`; those
745+
bind to the flow's `isInput` variables, and before this the screen suspended
746+
anyway — returning a form to a caller with no way to submit one, so the run
747+
parked forever.
748+
749+
The screen is treated as answered only when **all** of these hold:
750+
751+
- the caller named **at least one of this screen's own fields** — a run that
752+
named none of them is not driving the screen, and it renders as always;
753+
- every `required` field has a value (an empty or blank string is no value),
754+
supplied by that caller. A value that came from the trigger record, from an
755+
earlier node or from a declared `defaultValue` does not answer a `required`
756+
field; optional fields may come from anywhere;
757+
- the field has no `visibleWhen` the caller left unanswered. The server cannot
758+
evaluate that predicate — it has no rendered form and no collected values — so
759+
a conditional `required` field the caller did not name keeps the screen
760+
interactive. (The resume door makes the opposite call for the opposite reason:
761+
there, enforcing a hidden field's `required` would dead-end a run at Submit.)
762+
763+
Two screens never take this path, because they declare nothing to satisfy and a
764+
bag must not answer them vacuously: a **message-only** screen (no `fields`), and
765+
any screen whose author wrote `waitForInput: true` — that flag is an explicit
766+
"show this", and a confirmation step is not something a params bag may skip.
767+
768+
**Interactive runs are unaffected.** A console run supplies none of the screen's
769+
fields, so it renders the form exactly as before — including when the subject
770+
record happens to carry a column named like one of them, which is not the caller
771+
speaking. ⛔ `waitForInput: false` remains the wrong tool for the headless case:
772+
it skips the form for interactive users too.
773+
774+
⚠️ This does **not** make every screen flow completable from a headless caller. A
775+
call that omits the inputs still parks, and the MCP tool set has no verb to
776+
resume a parked run.
777+
778+
### `list_actions` publishes a flow action's inputs
779+
780+
An action's `params` in the MCP action listing come from its declared
781+
`params[]`. A `type: 'flow'` action rarely declares any — its input contract is
782+
the **target flow's `isInput` variables**, which is what the caller's `params`
783+
bag binds into. Those are published instead, in declaration order, carrying the
784+
`label`, `type`, `required` and select `options` of the screen field that
785+
collects each one. An action that declares its own `params[]` keeps them: the
786+
flow is read only when the action itself declares nothing.
787+
737788
### Parallel approvals — one aggregating node, not two pauses
738789

739790
"Finance **and** legal must both sign off, concurrently" is **one `approval`

0 commit comments

Comments
 (0)