- OPERATORS
- CONTROLLERS
Flow Router
v1.0.0newflow_router is a table-driven dispatcher for the LOPs flow system. It watches many incoming events, matches them against a live library table, and turns those matches into actions: set a parameter, pulse a button, call a method, update flow context, or move a flow_controller to another step.
Use it when behavior should be visible, editable, and data-driven instead of buried in one-off CHOP Execute DATs or custom callback code.
What You Can Use It For
Section titled “What You Can Use It For”flow_router is strongest when one project has many input phrases, channels, or command names that should map into a small set of behaviors.
Good uses:
- voice trigger command tables
- MIDI or OSC routing maps
- chat command routers
- STT transcript command handling
- mode-aware state-machine controls
- live operator control panels driven by DAT rows
- agent-authored behavior tables
- one place to audit “what input causes what action”
Use flow_action instead when there is one main signal and a short parameter-configured sequence is clearer than a table.
Use a normal callback DAT instead when the behavior is custom code with no useful table shape.
Mental Model
Section titled “Mental Model”flow_router has three layers:
- Input events arrive from a CHOP, a DAT, or both.
- The router matches each event against a library table and optional code route.
- Each matched row fires an action and may route the connected flow to another step.
The table is the main interface. Code and callbacks are optional.
Basic Setup
Section titled “Basic Setup”- Place
flow_routerinside the same component that yourflow_controllercontrols, or set Target Component if it lives outside that component. - Create or choose a table DAT for the route library.
- Set Config / Source to
Table. - Set Config / Library DAT to the table.
- Choose the event source on the Input page.
- Pulse Refresh.
- Trigger one input and inspect the internal
firedtable.
If the route table changes and Auto-Rebuild Index is on, the router updates automatically. If Auto-Rebuild is off, pulse Refresh after editing rows.
Library Table
Section titled “Library Table”The library is a DAT table. Row 0 is the header. Each row after that is one route rule.
Recommended header:
trigger | match_mode | action | target | par | value | method | args | goto | duringstep | context_key | context_value | enabledThe shipped router reads these columns by name. Missing columns become empty values, but complete tables are easier for humans and agents to edit correctly.
Column Reference
Section titled “Column Reference”trigger
The pattern to match. For CHOP input this is usually a channel name. For DAT input this is usually a phrase or command substring.
match_mode
How to compare the event name/text to trigger.
channel_name CHOP-only exact channel name matchexact exact string matchcontains case-insensitive substring matchregex Python regex searchaction
What the row does after matching.
set / setvalue set a parameterpulse pulse a parametercall call a promoted method on an operatorsendupdate write to flow_controller contextnoop log/match only; useful with gotocode execute trusted Python from the value celltarget
Target operator path. Absolute paths work. Relative paths resolve from the router parent first, then fall back to absolute lookup.
par
Target parameter name for set and pulse.
value
Value for set, or trusted Python source for code.
method
Method name for call.
args
JSON literal arguments for call. Use [] for no arguments.
goto
Flow route after the action.
stay no flow transitionnextstep next flow_controller stepnextroute no direct transition; useful with multi-match workflowsend_flow finish the flow<stepname> go to a named flow stepduringstep
Optional per-row step gate. If set, the row only fires while the connected flow_controller is on that step.
context_key
Key for sendupdate.
context_value
Value for sendupdate. Values starting with = are evaluated expressions with access to event, match, me, op, iop, and parent.
enabled
Blank, 1, true, yes, or on means enabled. 0, false, no, or off disables the row.
Input Sources
Section titled “Input Sources”CHOP Events
Section titled “CHOP Events”Use CHOP input when the event is a named channel changing state.
Set:
Input / Source: CHOP EventsInput / Input CHOP: your CHOPInput / CHOP Edge: Rising, Falling, Both, or Any Value ChangeConfig / Default Match Mode: Channel NameFeed stable 0/1 logic into the router when you care about edge timing. A Logic CHOP before the router is usually better than routing raw noisy values.
Example:
trigger | match_mode | action | target | par | value | method | args | goto | duringstep | context_key | context_value | enabledstart | channel_name | noop | | | | | [] | listening | idle | | | 1finish | channel_name | noop | | | | | [] | end_flow | listening | | | 1status | channel_name | sendupdate | | | | | [] | stay | | last_signal | =event["name"] | 1DAT Phrase Events
Section titled “DAT Phrase Events”Use DAT input when the event is a row of text: STT transcript rows, chat commands, typed command logs, or agent output tables.
Set:
Input / Source: DAT PhrasesInput / Input DAT: your DATInput / Text Column: column containing the phraseInput / DAT Edge: On Row Add or On Row ChangeConfig / Default Match Mode: ContainsFor STT, On Row Add is usually the safer default. Use On Row Change only if the source updates existing rows and you want each update to route.
Example:
trigger | match_mode | action | target | par | value | method | args | goto | duringstep | context_key | context_value | enabledboom | contains | set | /voice_trigger_ref/probe | Value0 | 42 | | [] | stay | | | | 1fire | contains | pulse | /voice_trigger_ref/probe | Trigger | | | [] | stay | | | | 1count | contains | set | /voice_trigger_ref/probe | Pulsecount | 7 | | [] | stay | | | | 1Action Recipes
Section titled “Action Recipes”Set a Parameter
Section titled “Set a Parameter”Use set for sliders, toggles, strings, menus, and numeric parameters.
calm | contains | set | /project/probe | Value0 | 0 | | [] | stay | | | | 1label | contains | set | /project/probe | Message | hello | | [] | stay | | | | 1enable | contains | set | /project/probe | Active | true | | [] | stay | | | | 1The router coerces 42 to int, 0.5 to float, true and false to bool, and leaves other values as strings.
Pulse a Parameter
Section titled “Pulse a Parameter”Use pulse for parameters with Pulse style.
fire | contains | pulse | /project/probe | Trigger | | | [] | stay | | | | 1Call a Method
Section titled “Call a Method”Use call when a behavior is already exposed as a method on an operator.
open probe | contains | call | /project/nav_helper | OpenViewer | | | ["/project/probe"] | stay | | | | 1Keep reusable Python in the target operator. Let the router choose when to call it.
Update Flow Context
Section titled “Update Flow Context”Use sendupdate when later flow steps need to know what was heard or triggered.
command | contains | sendupdate | | | | | [] | nextstep | listening | last_command | =event["name"] | 1The connected flow_controller receives the key/value through its context API.
Route Without an Action
Section titled “Route Without an Action”Use noop when the input only changes the state machine.
wake | contains | noop | | | | | [] | listening | idle | | | 1cancel | contains | noop | | | | | [] | idle | listening | | | 1Flow Controller Integration
Section titled “Flow Controller Integration”flow_router can work alone as a dispatcher, but it becomes more useful with flow_controller.
The router resolves a controller the same way flow_action does: it looks for the flow controller connected to the surrounding flow target. If the router is outside that target component, set Config / Target Component.
Useful patterns:
- router-level During gates the whole router to one flow step
- row-level
duringstepgates individual commands gotomoves the controller after a row firessendupdatestores route data in controller context
Example mode-aware phrase table:
trigger | match_mode | action | target | par | value | method | args | goto | duringstep | context_key | context_value | enabledwake | contains | noop | | | | | [] | listening | idle | | | 1boom | contains | set | /voice_trigger_ref/probe | Value0 | 42 | | [] | stay | listening | last_command | boom | 1done | contains | noop | | | | | [] | end_flow | listening | | | 1Agent + Tool DAT Pattern
Section titled “Agent + Tool DAT Pattern”An Agent LOP can use Tool DAT to edit a FlowRouter library. This is the clean pattern for user-authorable behavior:
Agent LOP -> Tool DAT edits library DAT -> flow_router dispatches the new rowThe agent should receive a compact contract:
- Read the router library table.
- Inspect the target operator.
- Verify the target parameter or method.
- Append a complete 13-column row.
- Do not overwrite the header.
- Use
containsfor phrase commands andchannel_namefor CHOP channels. - Use
set,pulse,call,sendupdate, ornoop. - Test by sending one event or ask the user to test.
This makes FlowRouter useful as a behavior layer that both humans and agents can edit without changing Python callbacks.
Use the micro guide in docs/agent_editing_contract.md as the prompt/reference for small models.
Config Source Modes
Section titled “Config Source Modes”Most projects should use Table.
This is the clearest and easiest mode to inspect, edit, and validate.
Use Code when the routing decision is algorithmic.
The Code DAT should define:
def route(event, ctx): return NoneExpected returns:
None no code overrideFalse drop the eventdict one match rowlist multiple match rowsUse Both when the table handles common routes and code handles exceptions.
Table matches first. Code can override, drop, or add route decisions.
Callbacks
Section titled “Callbacks”Callbacks are for observation, logging, and last-mile policy.
Pulse Create Callbacks to create a stub DAT.
Supported callback hooks:
onMatch after a table/code match is found, before actiononFire after the action runsonNoMatch when no row matchesonRoute before goto is appliedonLibraryChanged after library index rebuildUse callbacks for:
- debug logging
- confirmation policies
- route vetoes
- route overrides
- audit tables
Avoid using callbacks as the main routing table. If the behavior is a table, keep it in the library DAT.
Logging and Debugging
Section titled “Logging and Debugging”The internal fired DAT records route fires:
time | source | name | rule | action | target | par | statusUse it to answer:
- Did the input event arrive?
- Which row matched?
- Which action fired?
- Did the action report
set,pulsed,called,context_set,noop, or an error?
If nothing fires:
- Check Active is on.
- Check Input Source points at the correct CHOP or DAT.
- Check the library table is assigned.
- Pulse Refresh.
- Check
enabled. - Check
duringstepand router-level During. - Check
match_mode. - Check
Input Text Columnfor DAT inputs. - Check
CHOP Edgefor CHOP inputs.
If the row fires but the target does not change:
- Check
targetresolves. - Check
parspelling. - Check the action matches the parameter style.
- Check the
firedstatus cell.
Choosing Match Modes
Section titled “Choosing Match Modes”Use this as the default rule:
CHOP channel -> channel_namevoice phrase -> containstyped command -> contains or exactprotocol token -> exactcomplex text pattern -> regexAvoid regex unless it is actually needed. It is less readable for users and easier for agents to get wrong.
Multi-Match
Section titled “Multi-Match”Multi-match controls what happens when more than one row matches an event.
First Match Wins is safest for command routing.
Fire All Matches is useful for broadcast behavior, such as one command updating context and pulsing multiple targets.
When using Fire All Matches, keep row actions independent. If multiple rows write the same parameter or route the same flow, the table can become hard to reason about.
Safety
Section titled “Safety”set, pulse, sendupdate, and noop are the safest primitives.
call is safe when target methods are intentionally exposed and reviewed.
code is trusted Python. Do not use it for user-generated rows unless the project already trusts that author.
For voice or agent-authored routers, destructive commands should require a confirmation layer. Examples:
- delete operators
- reset all parameters
- quit TouchDesigner
- load another project
- save over files
Clean Example Shape
Section titled “Clean Example Shape”A clean public example should include:
flow_controllerflow_routerlibrary tableinput CHOP or input DATone visible target operatoroptional Agent LOP + Tool DAT that appends rowsShow these behaviors:
- A row sets a target parameter.
- A row pulses a target parameter.
- A row changes flow step.
- A row updates flow context.
- The
firedDAT proves which route fired. - An agent can append a row, then the next input uses it.
That example teaches the main value: behavior can be changed by editing a readable routing library instead of rewriting callback code.
Parameters
Section titled “Parameters”Router
Section titled “Router”op('flow_router').par.Status Str Connection status to FlowController
- Default:
"" (Empty String)
op('flow_router').par.Refresh Pulse Re-resolve iop.LOPflow, rebuild match index, repopulate step menus
- Default:
False
op('flow_router').par.Activate Toggle Master enable. When off, events are ignored.
- Default:
False
op('flow_router').par.Debouncems Int Minimum ms between same-rule fires. 0 = off.
- Default:
1500- Range:
- 0 to 10000
Config
Section titled “Config”op('flow_router').par.Libraryop DAT tableDAT with rule rows. Required when Configsource includes table.
- Default:
"" (Empty String)
op('flow_router').par.Codedat DAT textDAT defining route(event, ctx). Required when Configsource includes code.
- Default:
"" (Empty String)
op('flow_router').par.Matchcolumn Str Library column holding the match pattern
- Default:
trigger
op('flow_router').par.Matchmodecolumn Str Library column holding the match mode. Rows missing this fall back to Default Match Mode.
- Default:
match_mode
op('flow_router').par.Targetcomp OP Only set if flow_router is outside the flow_controller's Targetcomp. Relative '..' preferred when set.
- Default:
"" (Empty String)
op('flow_router').par.Autorebuild Toggle Rebuild library index automatically when the Library DAT changes. Disable to batch-edit rows and pulse Refresh once.
- Default:
True
op('flow_router').par.Inputchop CHOP CHOP whose channel transitions generate events. Channel name -> event name.
- Default:
"" (Empty String)
op('flow_router').par.Inputdat DAT Text DAT whose row changes/adds generate phrase events
- Default:
"" (Empty String)
op('flow_router').par.Inputtextcolumn Int Column index in the Input DAT holding the phrase text
- Default:
0- Range:
- 0 to 1
op('flow_router').par.Enablelogging Toggle Append every fire to the fired DAT
- Default:
False
op('flow_router').par.Logmaxrows Int Rolling log size
- Default:
50- Range:
- 10 to 10000
op('flow_router').par.Clearlog Pulse - Default:
False
Callbacks
Section titled “Callbacks”op('flow_router').par.Callbackdat DAT textDAT defining onMatch, onFire, onNoMatch, onRoute, onLibraryChanged
- Default:
"" (Empty String)
op('flow_router').par.Printcallbacks Toggle Debug: echo every callback fire to the textport
- Default:
False
op('flow_router').par.Createcallbacks Pulse Drop a stub callback DAT next to this op
- Default:
False
Changelog
Section titled “Changelog”v1.0.02026-05-02
- added library_watcher datexecute for auto-rebuild on library DAT changes - added docs (compose.json, guide.md, agent_editing_contract.md)
- added Toolname/Tooldescription pars for editable tool identity - added _prepare_for_release cleanup method - added library watcher parameters - expanded eval harness for voice trigger testing - updated category to Controllers
- set release_level to prod
- Initial flow_router structure
v0.1.0
# 0.1.0 — initial scaffold
- Submodule layout mirrored from flow_action
- FlowRouterEXT skeleton with tiered-config parameter surface
- Table tier MVP: CHOP channel_name + DAT text (contains/exact/regex) match, pulse/set/noop actions, first-match-wins
- ChainedCallbacksExt wired: onMatch, onFire, onNoMatch, onRoute, onLibraryChanged
- iop.LOPflow resolution and Duringstep filtering reuse flow_action's pattern
- CHOP + DAT input listeners (chopexec.py, datexec.py)
- Createcallbacks pulse drops a stub callback DAT next to the op