Creating Process Flows in the Thinkwise Software Factory
A process flow is Thinkwise's mechanism for chaining tasks, reports, connectors, and decisions into one deterministic sequence — either interactive (a user is walked through it) or, once every action in it is non-interactive, a system flow that runs headless on a schedule or via API. This skill covers the full lifecycle: the entity map, exact field-level creation steps verified against a live model, which action types are legal starting points, naming rules, control-procedure-backed logic, loops, layout, and translation — everything needed to build one through an MCP connector without guessing.
Follow the connector's standard discovery→act flow; never guess entity/field/enum names — confirm
them via get_entity_definition first. Domain key observed in this environment:
sf/manage_process_flows — try it directly first, and only escalate to
search_capabilities/get_available_domains on an entity_set_not_found/domain_not_found-style
rejection rather than re-discovering a domain that already resolved earlier this session.
Calling an external HTTP/REST API is its own skill, not covered end-to-end here: build the
connection and endpoint(s) via thinkwise_software_factory_web_connections (a different domain,
sf/manage_webconnections) first, then come back here to add the web_connection-type action that
calls it — see the action-type table below.
When to use a process flow at all
Use one when the application must coordinate multiple distinct actions and their order or outcome is
meaningful — a guided user sequence, a post-action continuation, an approval/confirmation flow, an
external integration pipeline, a file pipeline, a background queue worker, a scheduled
sync/cleanup, or a reusable subflow. Don't reach for one merely because several statements happen in
sequence — that's what a task's, subroutine's, or Default/Layout/Context control procedure's own logic
already covers; see references/process_flow_design_guide.md's "When to use a process flow" / "When
not to" sections before modeling the flow shell, plus the recurring user-flow and system-flow patterns,
process-procedure and process-variable design principles, subflow/transaction/error-handling/scheduling
guidance, and a final review checklist — everything below this point is the API mechanics for
whichever shape you land on.
Plan first
Before staging anything, outline the flow in plain language and get the user's explicit
confirmation — the same propose→confirm→build discipline thinkwise_software_factory_unit_tests
and thinkwise_software_factory_build_planner already use for their own artifacts, and a concrete
instance of thinkwise_software_factory_mcp_base's "Shared conventions" confirm-before-mutate rule
rather than a separate process invented here. Don't start "Build order" below until this step is
done.
State, in plain language — no entity names, no SQL:
- The trigger — what launches the flow (a task/table/report action, a schedule, a deep link, an
API call), and which eligible-type action will sit right after
start(see "Starting points" below). - The action sequence — the ordered list of real steps (tasks, reports, connectors, decision points) and what each one is for.
- Branch conditions — every point where the flow diverges (Success/Not-successful/Always, a message-option choice, a process-procedure route) and what decides each path.
- Loop and error handling — any repeated section, its exit condition and iteration cap, and what happens on failure at each step (retry, quarantine, visible failure) — see "Branching, parallelism, and loops" below and the design guide's "Error handling" section for the vocabulary.
Naming (see "Naming" below) and the loop exit condition/cap (see "Branching, parallelism, and loops" below) are two spots this often surfaces a genuine unknown rather than an obvious answer — raise those as open questions in the outline instead of silently picking something to keep moving.
Only once the user has explicitly confirmed the outline should proceed to "Build order" and start
staging process_flow/process_action rows. If something changes mid-build — a new branch, a
different trigger — revise the outline and re-confirm rather than patching around it silently.
Entity map
| Entity | Role | Key |
|---|---|---|
process_flow |
The flow itself — name, description, trigger settings | model_id, branch_id, process_flow_id |
process_action |
One node/step in the flow, typed by process_action_type |
model_id, branch_id, process_flow_id, process_action_id |
process_step |
One connector between two actions, with its branch condition | model_id, branch_id, process_flow_id, process_step_id |
process_variable |
Flow-scoped data passed between actions | model_id, branch_id, process_flow_id, process_variable_id |
process_flow_schedule |
A recurring trigger (system flows only, practically) | model_id, branch_id, process_flow_id, schedule_id |
process_action_start_object_available |
Which task/table/report can launch the flow at a given action | model_id, branch_id, process_flow_id, process_action_id, process_action_start_object_id |
role_process_flow_overview |
Per-role security, for the whole flow or one action | — |
process_flow_tag |
Free-form tags | — |
process_action, process_step, and process_flow_schedule are weak entities — stage them with
parent_entity_set: "process_flow" and parent_key: {model_id, branch_id, process_flow_id}.
process_variable stages flat (no parent needed — its detail nav target isn't independently
addressable through this connector, so pass the full key directly).
process_action_start_object_available cannot be written through this API at all, but this
doesn't block anything in practice — see "Starting points" below for why.
There is no dedicated "create process flow" bound task — process_flow is added directly via
stage_resource, unlike some other object types that need a bootstrap task first.
Build order
Actions must exist before steps can reference them; everything else has no hard ordering beyond its own parent.
process_flow(the flow shell)process_actionrows (every node, includingstartandstop— if the flow should be launchable by a task/table/report, include that as its own eligible-type action wired directly tostart, not as something wired up separately afterward — see "Starting points" below)process_steprows (the connectors between the actions from step 2)process_variablerows (if the flow needs to pass data between actions)process_flow_schedule(only if this will run unattended — see [[#Schedules]])- Translation of anything user-facing (see "Translation" below)
Creating process_flow
Minimal valid row: {model_id, branch_id, process_flow_id} — everything else defaults.
- Mandatory:
model_id,branch_id,process_flow_id, plus these which are pre-filled with sane defaults so you rarely need to touch them:use_starting_points(defaulttrue),use_api_trigger(defaulttrue, hidden field),custom_protocol(defaultfalse),deep_link_allowed(defaultfalse),multiple_running_instances_allowed(hidden, defaultfalse),iam_custom_schedule_allowed(hidden, defaultfalse). - Optional:
process_flow_description,alias_process_flow_id(hidden),custom_protocol_alias(hidden). process_flow_platformis read-only — auto-derived, not settable at creation.is_system_flowis hidden and never set directly — it's computed: the flow becomes a system flow automatically the instant everyprocess_actionin it is a non-interactive type (see thesystem_flow_actionflag inreferences/action_types.md). Don't try to patch this field — add a non-interactive action instead if the flow needs to qualify.
Creating process_action
Always mandatory, regardless of type: model_id, branch_id, process_flow_id, process_action_id,
process_action_type, use_processes, confirm_start, auto_confirm, process_action_mand.
x_coordinate / y_coordinate / width / height are editable but never mandatory — safe to
omit; set them later for layout (see "Alignment" below).
Per-type FK requirements (confirmed by patching process_action_type and re-reading field states —
check references/action_types.md for the full ~100-value catalog before assuming a type's exact
name):
process_action_type |
Extra mandatory fields | Notes |
|---|---|---|
start (98) |
none | Pure marker; use_processes itself becomes hidden for this type |
stop (99) |
none | Pure marker |
execute_task (60) |
task_id (mandatory) |
task_variant_id optional |
execute_tab_task (6) |
tab_id (mandatory), tab_task_id (mandatory) |
tab_variant_id stays hidden/optional until tab_id is set |
activate_detail (1) |
tab_id (mandatory), ref_id (mandatory) |
tab_id must be the detail/target table of the reference (the ref's target_tab_id), not the source/context table — setting tab_id to the source table makes ref_id reject writes. Does not open as a popup/modal; it activates a detail inline in the current screen tree — see open_document below for a modal |
open_document (2) |
tab_id (mandatory) |
tab_variant_id optional; ref_id hidden/unused. The actual modal-popup mechanism — see "Wiring runtime values through process variables" below for the fixed-input parameter that makes it open floating/modal |
change_filter (330) |
tab_id (mandatory) |
The filter condition itself is not a plain field — see "Wiring runtime values through process variables" below |
web_connection (602) |
web_connection_id (mandatory), web_connection_endpoint_id (mandatory) |
The preferred choice for any HTTP/REST call — build the connection and its endpoint(s) first, via thinkwise_software_factory_web_connections (domain sf/manage_webconnections, a different domain from this one). An earlier version of this note, based on inspecting web_connection/web_connection_endpoint only through sf/manage_process_flows (where a process action merely references a connection/endpoint by id), wrongly concluded the objects exposed "almost no writable configuration" — read through the correct domain, they carry full configuration (base URL, auth, method, path, body, headers, output parsing) and are fully buildable through the API. Once the connection exists, this action's own inputs/outputs are the pre-seeded process_action_web_connection_endpoint_parmtr_input_parmtr / process_action_web_connection_parmtr_input_parmtr (in) and process_action_modeler_web_connection_endpoint_output (out) rows — see that skill for the full field/enum reference and worked examples. The domain also exposes a bound migration task on this action, task_enrichment_conv_http_connector_to_web_connection, for converting an existing http_connector action. |
http_connector (600) |
none at the top level | Fallback only — reach for this over web_connection only for a genuine one-off call that will never be reused, or a documented gotcha a web connection hits (e.g. a reported multipart-form-on-GET issue — see thinkwise_software_factory_web_connections). Its configuration is not a separate child entity graph — it lives in the same process_action_modeler_fixed_input/process_action_modeler_fixed_output mechanism used elsewhere (see "Wiring runtime values through process variables" below), with input_parmtr_id/output_parmtr_id values like http_con_url, http_con_http_method, http_con_content pre-seeded and ready to edit. It has no reuse, no per-environment override, and no built-in response parsing — all reasons to default to web_connection instead. |
decision (100) |
none | See "Decision as a code-only step" below — this is also how you add a step that purely runs logic |
Gotcha: this is a process_action-specific instance of the general "a multi-field write can
silently drop one field, with no error" behavior documented in thinkwise_datamodeling_guidelines's
quirks section — patching several fields on a fresh process_action in one call — especially the
composite key fields (model_id/branch_id/process_flow_id) alongside process_action_id, or
process_action_id alongside process_action_type — can leave a later field in that same call
silently reset to null in the response. Separately, and not limited to batched calls: patching
process_action_type or (for table-bound types) tab_id or task_id on its own can overwrite
process_action_id with an auto-suggested id derived from the type/table/task — e.g. setting
tab_id on an activate_detail action silently renamed it to activate_detail_employee_absence,
and setting task_id on an execute_task action equally renames it to execute_task_<task_id> —
discarding whatever id had been set moments before. Order process_action_id last among the properties you set — after process_action_type and
tab_id/task_id — and this is fixable in one combined stage_resource/patch_resource call, not
several: verified live (RK_SCHEDULER_TEST), staging a fresh execute_task action with
process_action_type, task_id, process_action_id, process_action_description, and four more fields
all in one call, in that order, committed with every field intact — process_action_id was not
overwritten and the description did not revert. Re-check the returned fields block on that one call
before moving on, rather than assuming it landed — but there's no need to split the write into several
isolated single-field calls first; try the ordered combined call and verify its result before falling
back to isolation.
process_action_type is an Edm.Int32 enum keyed by numeric values (start=98, execute_task=60,
…) — patching it with the string label ("start") can be rejected as an invalid value; pass the raw
integer instead (forcing a data-value interpretation on the connector if it supports one).
Creating process_step
Mandatory: model_id, branch_id, process_flow_id, process_step_id, last_process_action_id,
next_process_action_id, last_process_action_successful (defaults to always=2),
process_order_input (default true), process_order_output (default true).
last_process_action_id/next_process_action_idare plain string FKs toprocess_action_id— no lookup/resolve step, just pass the exact id string. The referencedprocess_actionrows must already exist.last_process_action_successfulenum:not_successful= 0,successful= 1,always= 2 — this is the green/red/blue branch condition from the designer canvas.order_nois not mandatory (defaults to50);abs_order_nois read-only and auto-computed — don't try to set it.- A single action can have several outgoing
process_steprows; per §"Branching and loops" below, multiple outgoing steps run in parallel, and any step'snext_process_action_idcan point at an action earlier in the flow to form a loop.
An error message emitted during a task does not necessarily mark the process action unsuccessful —
test the actual action status, not the presence of a message. If routing depends on a real business
outcome, return/map an explicit status variable, or confirm the task's own abort semantics genuinely
produce the unsuccessful state the flow is routing on. See
references/process_flow_design_guide.md's "Branching" section for the fuller reasoning on
Success/Not-successful/Always usage.
Inserting an action into an existing chain is manual, not automatic. Adding a new process_action
between two already-connected ones doesn't re-splice the existing process_step for you — explicitly
edit the existing step's next_process_action_id to point at the new action, then add a new step from
the new action to whatever the old step used to point at.
Renaming a process_action does cascade automatically, though: the rename operation updates every
process_step.last_process_action_id/next_process_action_id that referenced the old id, and the
step's own generated id, with no manual follow-up needed.
Creating process_variable
Mandatory: model_id, branch_id, process_flow_id, process_variable_id, dom_id (a real domain
— required, no default), type_of_default_value (default constant_value=0; other value is
expression=1), available_in_deep_link (default false), mand_in_deep_link (default false),
process_input (default true), process_output (default true), sub_flow_input (default
false), sub_flow_output (default false).
Optional: default_value (used when type_of_default_value=constant_value), default_value_query
(hidden unless type_of_default_value=expression), process_variable_description.
For API-triggered system flows, process_property binds a variable directly to the HTTP context
instead of a normal default: request_method(0), request_path(1), request_query_string(2),
request_headers(3), request_body(4), response_code(5), response_headers(6),
response_body(7).
Wiring runtime values through process variables
An action's actual inputs/outputs are junction entities named
process_action_modeler_<kind>_<input|output> — confirmed live: process_action_modeler_fixed_input/
process_action_modeler_fixed_output (literal/enum config values and their captured results, e.g. a
connector's URL in, its response body out, or open_document's floating/modal switch),
process_action_modeler_col_input/_col_output (a table column, e.g. a change_filter condition or
a captured row value), process_action_modeler_task_parmtr_input/_task_parmtr_output (a task's own
parameters), plus sub_flow/report_parmtr/message_broker_message variants for those action
types — pick the one matching what the action actually is, not one generic table for all of them.
For a web_connection action specifically, the equivalents are
process_action_web_connection_endpoint_parmtr_input_parmtr (endpoint input),
process_action_web_connection_parmtr_input_parmtr (connection-level input), and
process_action_modeler_web_connection_endpoint_output (output) — see
thinkwise_software_factory_web_connections for the full field reference.
A generically-named process_action_output_parmtr entity also exists and looks like "the" output
entity from its own definition (it even carries the same output_parmtr_id enum as
process_action_modeler_fixed_output) — but add against it is rejected outright (403) through this
API, confirmed live. It appears to be a newer, unified/read-oriented scheme that isn't (yet) a valid
write path here. For capturing a connector-type action's output (http_connector, ftp_connector,
db_connector, etc.) into a variable, use process_action_modeler_fixed_output instead — same
pre-seeded/edit-only pattern as process_action_modeler_fixed_input below, keyed by output_parmtr_id
(e.g. http_con_content for an HTTP response body), with a process_variable_id field to set.
These rows are pre-seeded, not freely addable. The moment a process_action's type (and, for
column-based ones, its tab_id) is set, one placeholder row per relevant column/parameter already
exists — e.g. one _col_input row per column of the action's table, one _fixed_input row per input
parameter that type supports. Staging an add on any of these child entities is rejected outright
(before you even get to set a field) — query for the existing placeholder row first (filter by
process_action_id), then stage an edit against its full composite key (including
tab_id/col_id/input_parmtr_id/task_parmtr_id as applicable) instead.
Worked pattern — filtering a popup to the row that triggered the flow (the shape behind "double-click a row → modal popup of related, filtered records"):
- The trigger is normally an
execute_tab_taskaction running a table task bound viagrid_double_click. The row's data does not reach the flow throughprocess_action_modeler_col_outputon this action — that entity stages and commits without error but silently captures nothing usable for anexecute_tab_task. Instead, add atask_parmtron the underlying table task itself (e.g.employee_id) with bothtask_input=true(so atab_task_parmtrbinding can auto-fill it from the selected row's column) andtask_output=true(so its value becomes readable by the flow). - Create a
process_variablewith a matchingdom_id. - On the triggering
execute_tab_taskaction, edit its pre-seededprocess_action_modeler_task_parmtr_outputrow for thattask_parmtr_id, settingprocess_variable_idto the variable — this is what actually carries the selected row's value into the flow. - Open the popup with an
open_documentaction, then follow it with achange_filteraction on the same table. Edit that action's pre-seededprocess_action_modeler_col_inputrow for the FK column, setassignment_method='variable'(process_variable_idoften auto-resolves on an unambiguous name/domain match, but verify it). - For the popup's modal behavior itself:
open_documentactions get a pre-seededprocess_action_modeler_fixed_inputrow keyed byinput_parmtr_id='open_doc_floating'— edit it toassignment_method='literal_constant',constant_enum_value='open_doc_floating_modal'.
A given process_variable can only be the output target of one binding per action. Assigning two
different output rows on the same action to the same variable (e.g. both a col_output and a
task_parmtr_output pointed at the same variable, left over from an earlier wrong attempt) causes a
real duplicate-key failure on the second commit — clear/null the first binding before setting the
second.
Schedules
Weak entity under process_flow. Mandatory: model_id, branch_id, process_flow_id,
schedule_id, recurrence_type (default daily=0; other values weekly=1, monthly=2),
recurrence_day (default 1), occur_type (default once=0; other values recurring=1,
recurring_all_day=2), occur_once (mandatory only when occur_type=once — a TimeOfDay).
Day-of-week flags (monday…sunday) are hidden and only relevant when recurrence_type=weekly;
month-position fields only matter when recurrence_type=monthly. Minimal daily-at-a-fixed-time
schedule: {model_id, branch_id, process_flow_id, schedule_id, occur_once: "02:00:00"} with
everything else left at its default.
Schedules only make practical sense once the flow qualifies as a system flow (every action non-interactive) — a schedule on a flow that still has an interactive action has nothing meaningful to fire unattended.
Starting points — what can launch a flow, and a hard write-API limitation
process_action_start_object_available records which real object (a table row, a report, a task) can
launch the flow, landing execution at a specific process_action rather than always at the literal
start node. It references process_action_id directly; which kind of object is the candidate is
expressed by which of its FK-shaped columns is populated (tab_id/tab_variant_id,
report_id/report_variant_id, task_id/task_variant_id) — there's no separate discriminator
column.
Only eight process_action_type values are ever observed as starting points, confirmed against
real data across ten live models:
| Type | Why it can start a flow |
|---|---|
activate_detail (1) |
Opening/activating a detail is itself a user launch point |
open_document (2) |
Opening a document can be the trigger |
add_record (3) |
Creating a new row can launch a flow (e.g. a wizard) |
edit_record (4) |
Editing a row can launch a flow |
delete_record (5) |
Deleting a row can launch a flow |
execute_tab_task (6) |
The overwhelming majority of real starting points — any table task |
execute_task (60) |
A standalone task launching a flow |
execute_system_task (61) |
A system task launching a (system) flow |
Every other type — including start, stop, execute_tab_report/execute_report/generate_report,
activate_grid/activate_form, decision, and every connector type — was never observed as a
starting point. start/stop are excluded because they're the flow's own implicit entry/exit, not
things a user or API launches into; reports have schema support (report_id/report_variant_id
columns exist) but weren't exercised in any live model sampled — treat report-triggered starts as
theoretically schema-supported but unverified in practice.
process_action_start_object_available itself is read-only through this MCP write API — both
add and edit via stage_resource return 403/staging_request_failed, even against an existing
row, and the only bound write operation found, task_reset_process_action_start_object (zero
parameters), clears a starting point rather than selecting one. This is not actually a blocker,
and no manual step in the Software Factory's own UI is needed (correcting an earlier version of this skill that claimed
otherwise): the trigger object simply needs to be modeled as its own eligible-type action wired
directly to start inside the flow. Any action of one of the eight types above that sits
immediately after start is automatically usable as a launch point for its underlying task/table/
report — process_action_start_object_available reflects that structural fact rather than being a
separate switch you flip. Confirmed live (2026-07-28): a menu-triggered flow needs an execute_task
action (task_id = the menu's trigger task) as the first action after start; putting the trigger
task outside the flow and hoping to "wire it up" separately (via the Software Factory's own UI or
otherwise) is the mistake to avoid — build it into the flow's own action chain instead.
Process action types
The full ~100-value catalog, grouped by category with each type's system_flow_action eligibility,
is in references/action_types.md — read it before picking a type for a new action, rather than
guessing a name. Roughly 57 of the ~100 types are legal inside a system flow (essentially every
connector, decision, execute_system_task/execute_system_sub_flow, AI/ML, IAM, file, and
start/stop); UI-bound types (activate_form, add_record, edit_record, execute_tab_task,
filter/sort/grid-navigation, etc.) are not, since a system flow runs with no session to act on.
There is no dedicated "control procedure" action type. Control-procedure logic never gets chosen directly on the canvas — see "Process logic" below for exactly how it attaches.
Decision as a code-only step
decision normally routes execution based on a condition, but it's also the right action to reach
for whenever a step's only job is running code — with use_processes checked and a single outgoing
process_step set to always (no real branching), it's a clean way to run a control procedure
(sum a total into a process variable, validate something, transform data) between two other actions
without any task, report, or connector attached to it at all.
Example: a decision action calculate_totals, use_processes=true, one outgoing step at
last_process_action_successful=always to the next action — its assigned control procedure sums
order lines into a process variable that a later execute_task action then consumes via
{order_total} substitution.
This is one specific use of a process procedure. For the fuller decision — when a process procedure is
warranted at all vs. when it's the wrong place for the logic, and the best-practice checklist for
writing one — see references/process_flow_design_guide.md's "When to use a process procedure"
section.
Show message (show_msg) — presenting a message and branching on the choice
process_action_type = show_msg (enum value 350) presents a modeled message (set msg_id on the
action) and, for a choice message, lets the flow branch on which msg_option the user picked. Full
message-modeling mechanics (severity, location, options, database capture, calling from SQL) live in
thinkwise_software_factory_messages — load that skill before creating or editing the msg/msg_option
rows themselves; this section covers only how the action wires into the flow.
The ordinary last_process_action_successful step condition (not_successful/successful/always)
is too coarse the moment a message has more than one affirmative or more than one negative option —
it can't distinguish, say, yes (status_code=0) from yes_always (status_code=1). To route on the
exact chosen option, capture its numeric status code into a process variable and branch on that instead:
the show_msg action has a pre-seeded process_action_modeler_fixed_output row keyed
output_parmtr_id='status_code' (the same generic status-code output every action type exposes, not
msg-specific) — following the usual pre-seeded/edit-only pattern above, query for that existing row
filtered by process_action_id, edit it to set process_variable_id, then follow with decision
action(s) testing that variable against each option's msg_option_status_code. For a plain single
yes/no choice, the ordinary successful/not_successful step condition on the two outgoing
process_steps is enough and the status-code capture is unnecessary.
Branching, parallelism, and loops
Multiple outgoing process_step rows from one action fan out and run in parallel; a later action
that several branches converge into only fires once every parallel branch feeding it has completed.
Only one flow instance is active per user at a time.
A loop is just a process_step whose next_process_action_id points at an action earlier in the
same flow — nothing in the schema distinguishes a "loop" connector from any other; it's drawn (or
staged) exactly the same way, just aimed backwards. The standard shape is two pieces: the action(s)
doing repeated work, and a decision that tests a process variable each pass and either loops back or
lets execution continue.
Example — paging through an HTTP API until there's no more data: variables page_number (int,
default 1), has_more_pages (bool), iteration_count (int, default 0); a web_connection action
get_page (endpoint path/query string uses {page_number} — see
thinkwise_software_factory_web_connections); an execute_task action
store_page_results that saves the page and increments both page_number and iteration_count
(via its control procedure); a decision action check_more_pages whose successful step loops back
to get_page when has_more_pages=true, and whose not_successful step continues to stop.
Warning: nothing in the model stops a loop from running forever — there's no built-in step-count fuse, and "one flow instance per user" is no protection for a system flow: a scheduled flow that loops without ever satisfying its exit condition keeps its Indicium worker busy indefinitely, and a recurring schedule can stack further runs on top if
multiple_running_instances_allowedis on. Prevention strategy: never let the loop depend on the business condition alone — add an unconditional counter variable incremented every pass, AND the real exit condition with a harditeration_count < max_iterationscap, treat hitting the cap as a distinct alertable outcome (set an output variable and route it to a notification/log action) rather than silent truncation, and prove the exit condition with a small cap in the Process flow monitor before raising it to a realistic ceiling.
The exit condition and the iteration cap are business decisions, not implementation defaults —
if the user hasn't said what business condition should end the loop or what max_iterations
ceiling is safe, ask rather than picking a number unilaterally, per
thinkwise_software_factory_mcp_base's "Shared conventions" ask-don't-default rule. The "prove the
exit condition with a small cap" technique above is a testing step for validating a cap/condition
the user has already settled on — it doesn't substitute for asking what that cap/condition should
be in the first place.
Every path must end at stop
Check every branch — including message/error branches and loop exits — actually reaches a stop
action, not just the main line. A path that dead-ends on a non-stop action leaves that flow instance
incomplete. Treat this as a mandatory check before considering any flow done, the same way you'd check
every process_step you added actually has a valid next_process_action_id.
Process logic and control procedures
Two distinct mechanisms carry real SQL logic in a process flow, and confusing them is the most common mistake:
- The action's own
use_processesflag ("Use process procedure") opts that specificprocess_actioninto thePROCESSEScode group — a control procedure assigned here runs as part of the action itself (see "Decision as a code-only step" above for the most common use of this). - The far more common pattern: an
execute_task/execute_system_taskaction simply calls a task whose own logic type is Stored procedure, and the task's control procedure (in theTASKScode group) is where the real work happens — parsing a connector's response, writing rows, etc.
Either way, creating and assigning that control procedure follows the full lifecycle documented in
thinkwise_software_factory_create_control_procedures — load that skill before writing the actual
SQL: check branch_rdbms_type first, scaffold with Generate code group before hand-guessing the
business-logic variables available to a PROCESSES/TASKS code-group procedure, then assign and
verify by reading the generated code, not just trusting a "successful" status.
Alignment
Layout lives entirely on process_action: x_coordinate, y_coordinate, width, height — plain
numbers, no separate design table, no persisted grid-snap metadata (snapping is designer-UI behavior
only). A convention taken from a real flow's actual coordinates: keep width/height constant across
every action, run the main "happy path" along one horizontal lane (y_coordinate unchanged), and step
x_coordinate by a fixed increment (~48–56 units) per action. Give a decision's branches their own
row above/below the main lane, then reconverge them back onto the main y before stop. Since none of
this is mandatory, it's safe to omit at creation time and set once the flow's logic is proven — but do
set it before calling the flow finished, since an unlaid-out canvas is hard for the next person (or
agent) to read.
Naming
A process flow's process_flow_id must never reuse an existing task's task_id or a report's
report_id/tab_report_id in the same model. Nothing in the write API actually rejects this
collision — staging a process_flow with an id identical to an existing task's id was accepted
without a validation warning — which is exactly why this has to be enforced by discipline rather than
relied on as a platform guarantee: query existing task and report/tab_report ids in the target
model first and confirm no overlap before picking a process_flow_id.
Before naming a new flow, look at the other process flows already in the model and match whatever local convention they establish — conventions observed vary genuinely by model/team, so there is no single universal answer:
Plain business flows (the common case): snake_case, no fixed prefix, named for the outcome or trigger rather than a strict word order —
sales_invoice_approve_print,calculate_and_store_route,customer_create_contact_person,deep_link_to_sales_invoice. Pick whichever ordering (object_verb vs. verb_object vs. outcome-first) the sibling flows in this model already use, rather than introducing a new one.System/background flows, when a project wants them visually distinguishable from interactive ones, commonly take a
system_flow_prefix (system_flow_clean_up,system_flow_automatic_thinkstore_refresh) — Thinkwise's own built-in framework flow (tsf_system_flow_run_tsf_optimize) usestsf_system_flow_for the same reason. This is a chosen convention, not an enforced one: plenty of real system flows (e.g.save_customer_coordinates,check_uta_vm_status) have no such prefix at all —is_system_flowis a computed property, never something the name has to signal.Some teams use a lighter
flow_orpf_micro-prefix instead, especially for many small, similar flows following one template (flow_copy_<x>_to_new_recordrepeated per table).All observed names are lowercase snake_case — no camelCase, no spaces, ever.
If the model has no existing process flows to pattern-match against and no other convention is obvious, don't default silently — per
thinkwise_software_factory_mcp_base's "Shared conventions" (ask, don't default), propose plain outcome-descriptive snake_case with no prefix as your recommendation (matching general Thinkwise naming guidelines — seethinkwise_datamodeling_guidelines, purpose over plumbing, same principle as control procedure naming) and get the user to confirm or correct it before committing to aprocess_flow_id.
Translation
The flow's own description, its actions' descriptions, any message text, and process variable
descriptions are all translatable objects. Don't leave newly-created text in the source language only
— load thinkwise_software_factory_translation_objects for the transl_object/transl_object_transl
mechanics and the approval workflow before considering a new process flow finished.
Pre-flight checklist
- Confirm the domain key via
search_capabilities/get_available_domainsrather than assumingsf/manage_process_flowsholds for this connector. - Query existing
task/reportids in the target model before picking aprocess_flow_id— no collision is enforced automatically. - Look at sibling process flows already in the model and match their naming convention before introducing a new one.
- Create
process_actionrows before anyprocess_stepthat references them. - Set a fresh
process_action's fields in one combined call, ordered soprocess_action_typeandtab_id/task_idcome beforeprocess_action_id— verified live, this lands every field correctly in a single round trip. Re-read the returnedfieldsblock on that call before moving on. - Pick the right per-type mandatory FK for each
process_action(see the type table above) — default toweb_connectionfor any HTTP/REST call (seethinkwise_software_factory_web_connectionsto build the connection first); reach forhttp_connectoronly as a documented fallback for a genuine one-off call or a specific gotcha. - Don't try to patch
is_system_floworprocess_flow_platformdirectly — both are computed/read-only. - A flow's trigger object (task/table/report) must be its own eligible-type
process_actionwired directly tostart— not left outside the flow.process_action_start_object_availablecan't be written via this API, but that's fine: it's a structural reflection of the action chain, not a separate switch, so no manual step in the Software Factory's own UI is needed once the trigger action sits right afterstart. - Check every branch (including loops and error paths) actually reaches a
stopaction. - A
duplicate_key-style error on commit is not a reliable signal either way — it has been observed both as a false negative (
…(truncated)