Workflows
Workflows run agents in a defined structure. You define the steps and their order. The run is deterministic.
Workflows reference profile agents by their agent_code to create deterministic pipelines.
Sequential workflow
Section titled “Sequential workflow”Agents run one after another. UC AI stores the output of each step under its output_key. A later step reads this output with the {$.steps.<output_key>} syntax.
DECLARE l_workflow_id NUMBER; l_session_id VARCHAR2(100); l_result json_object_t; l_workflow_def CLOB;BEGIN -- Define the workflow l_workflow_def := '{ "workflow_type": "sequential", "steps": [ { "agent_code": "math_agent", "input_mapping": { "question": "{$.input.question}" }, "output_key": "step1_result" }, { "agent_code": "summarizer_agent", "input_mapping": { "text": "{$.steps.step1_result}" }, "output_key": "step2_result" } ] }';
-- Create the workflow agent l_workflow_id := uc_ai_agents_api.create_agent( p_code => 'math_summary_pipeline', p_description => 'Solves a math question then summarizes the answer', p_agent_type => uc_ai_agents_api.c_type_workflow, p_workflow_definition => l_workflow_def, p_status => uc_ai_agents_api.c_status_active ); commit; -- agents must be committed before they can be executed
-- Execute l_session_id := uc_ai_agents_api.generate_session_id; l_result := uc_ai_agents_api.execute_agent( p_agent_code => 'math_summary_pipeline', p_input_parameters => json_object_t('{"question": "What is 7 + 8?"}'), p_session_id => l_session_id );
DBMS_OUTPUT.PUT_LINE('Result: ' || l_result.get_clob('final_message'));END;/How it works:
math_agentreceives{"question": "What is 7 + 8?"}from the input- UC AI stores its output under
step1_result summarizer_agentreceives{"text": "<answer of the math agent>"}from the output of the previous step- The final result is the output of the last step
Conditional workflow
Section titled “Conditional workflow”Steps run in order, the same as in a sequential workflow. Each step can also have a condition. A step runs only when its condition is true. If the condition is false, UC AI skips the step.
The condition is a string with a PL/SQL boolean expression. UC AI first resolves each {$.path} placeholder against the workflow state. It then evaluates the expression. A placeholder resolves to plain text, so put single quotes around it for a comparison with a string value.
DECLARE l_workflow_id NUMBER; l_result json_object_t; l_workflow_def CLOB;BEGIN l_workflow_def := q'[{ "workflow_type": "conditional", "steps": [ { "agent_code": "geography_agent", "condition": "'{$.input.category}' = 'geography'", "input_mapping": { "question": "{$.input.text}" }, "output_key": "geo_result" }, { "agent_code": "summarizer_agent", "condition": "'{$.input.category}' = 'summary'", "input_mapping": { "text": "{$.input.text}" }, "output_key": "sum_result" } ] }]';
l_workflow_id := uc_ai_agents_api.create_agent( p_code => 'category_router', p_description => 'Routes a request to the matching specialist agent', p_agent_type => uc_ai_agents_api.c_type_workflow, p_workflow_definition => l_workflow_def, p_status => uc_ai_agents_api.c_status_active ); commit; -- agents must be committed before they can be executed
l_result := uc_ai_agents_api.execute_agent( p_agent_code => 'category_router', p_input_parameters => json_object_t('{"category": "geography", "text": "What is the capital of France?"}'), p_session_id => uc_ai_agents_api.generate_session_id );
DBMS_OUTPUT.PUT_LINE('Result: ' || l_result.get_clob('final_message'));END;/How it works:
- UC AI evaluates the
conditionof each step against the current workflow state before the step runs - With
category = 'geography', onlygeography_agentruns. UC AI skips the summarizer step - Steps without a
conditionalways run - The final result is the output of the last step that ran
A condition can also reference the output of an earlier step. A common pattern starts with a classifier step without a condition, and a response schema. The later steps then use its structured output in their conditions:
"condition": "'{$.steps.classification.category}' = 'complaint'"The condition is a PL/SQL expression, so every boolean expression works. Examples are a numeric comparison such as "{$.steps.classification.confidence} >= 0.8", and a function call.
Put single quotes around a string reference, as in '{$.steps.x}' above. Leave a numeric reference without quotes, as in {$.steps.x} >= 8. The same convention applies to exit_condition, and to an input_mapping entry with "is_plsql_expression": true.
Loop workflow
Section titled “Loop workflow”Agents run in a loop. The loop ends at an exit condition, or at the maximum number of iterations. Use a loop to refine an output until it reaches a quality level.
DECLARE l_workflow_id NUMBER; l_result json_object_t; l_workflow_def CLOB;BEGIN l_workflow_def := '{ "workflow_type": "loop", "pre_steps": [ { "agent_code": "haiku_creator_agent", "input_mapping": { "topic": "{$.input.topic}" }, "output_key": "current_haiku" } ], "steps": [ { "agent_code": "haiku_rater_agent", "input_mapping": { "haiku": "{$.steps.current_haiku}", "topic": "{$.input.topic}" }, "output_key": "haiku_rating" }, { "agent_code": "haiku_improver_agent", "input_mapping": { "topic": "{$.input.topic}", "feedback": "{$.steps.haiku_rating.rating_feedback}", "haiku": "{$.steps.current_haiku}" }, "output_key": "current_haiku" } ], "post_steps": [ { "agent_code": "haiku_translator_agent", "input_mapping": { "language": "german", "haiku": "{$.steps.current_haiku}" }, "output_key": "translated_haiku" } ], "loop_config": { "max_iterations": 3, "exit_condition": "{$.steps.haiku_rating.quality} >= 8" }, "final_message": "{$.steps.translated_haiku}" }';
l_workflow_id := uc_ai_agents_api.create_agent( p_code => 'haiku_refinement', p_description => 'Creates, refines, and translates a haiku', p_agent_type => uc_ai_agents_api.c_type_workflow, p_workflow_definition => l_workflow_def, p_max_iterations => 3, p_status => uc_ai_agents_api.c_status_active ); commit; -- agents must be committed before they can be executed
l_result := uc_ai_agents_api.execute_agent( p_agent_code => 'haiku_refinement', p_input_parameters => json_object_t('{"topic": "Star Wars"}'), p_session_id => uc_ai_agents_api.generate_session_id );
DBMS_OUTPUT.PUT_LINE('Final haiku: ' || l_result.get_clob('final_message'));END;/How it works:
- Pre-steps run once before the loop: the haiku creator generates an initial haiku
- Loop steps repeat: the rater scores the haiku, then the improver corrects it with this feedback
- The loop exits when
quality >= 8or after 3 iterations - Post-steps run once after the loop: the translator converts the final haiku to German
final_messagedefines which step output becomes the overall result
Using structured output for exit conditions
Section titled “Using structured output for exit conditions”A loop exit condition usually depends on a structured value in the answer of an agent. The haiku rater above uses a response schema. It returns a JSON object with the fields quality and rating_feedback:
DECLARE l_profile_id NUMBER; l_schema CLOB := '{ "type": "object", "properties": { "quality": { "type": "number", "minimum": 1, "maximum": 10, "description": "Quality rating from 1 to 10" }, "rating_feedback": { "type": "string", "description": "One-sentence feedback for improvement" } }, "required": ["quality", "rating_feedback"] }';BEGIN l_profile_id := uc_ai_prompt_profiles_api.create_prompt_profile( p_code => 'haiku_rater_profile', p_description => 'Rates haikus on a 1-10 scale', p_system_prompt_template => 'You are a haiku critic. Rate haikus based on form, imagery, and emotional impact.', p_user_prompt_template => 'Rate this haiku about "{topic}": {haiku}', p_provider => uc_ai.c_provider_openai, p_model => uc_ai_openai.c_model_gpt_5_6_luna, p_response_schema => l_schema, p_status => uc_ai_prompt_profiles_api.c_status_active );
COMMIT;END;/The workflow references the structured fields directly:
"exit_condition": "{$.steps.haiku_rating.quality} >= 8"Loop without pre/post steps
Section titled “Loop without pre/post steps”For a simpler loop, put every step in steps. Use the optional flag for the first iteration, because the feedback does not exist yet:
{ "workflow_type": "loop", "steps": [ { "agent_code": "haiku_creator_agent", "input_mapping": { "topic": "{$.input.topic}", "feedback": { "path": "{$.steps.haiku_rating.rating_feedback}", "optional": true } }, "output_key": "haiku_result" }, { "agent_code": "haiku_rater_agent", "input_mapping": { "haiku": "{$.steps.haiku_result}", "topic": "{$.input.topic}" }, "output_key": "haiku_rating" } ], "loop_config": { "max_iterations": 3, "exit_condition": "{$.steps.haiku_rating.quality} >= 8" }}In the first iteration, feedback does not exist, so UC AI omits it. From the second iteration, UC AI passes the feedback of the rater to the creator.
PL/SQL steps
Section titled “PL/SQL steps”Not every step needs an LLM. A PL/SQL step runs your own code inside a workflow. Use it to reshape the output of one agent for the next agent, to read a value from a table, to calculate, to format, or to gate the rest of the pipeline. It makes no model call, so it costs nothing and it is deterministic.
Set "step_type": "plsql" and give a plsql_function_call. This is a PL/SQL function body that returns a CLOB, the same as a tool. It can hold at most one bind variable, by convention :parameters. This variable receives the whole workflow state as a JSON CLOB: { "input": {...}, "steps": {...} }. A PL/SQL step has no input_mapping. The code reads what it needs from the state.
DECLARE l_workflow_id NUMBER; l_result json_object_t; l_workflow_def CLOB;BEGIN l_workflow_def := q'[{ "workflow_type": "sequential", "steps": [ { "agent_code": "extractor_agent", "input_mapping": { "text": "{$.input.document}" }, "output_key": "extracted" }, { "step_type": "plsql", "plsql_function_call": "declare s json_object_t := json_object_t(:parameters); o json_object_t := json_object_t(); begin o.put('amount_eur', s.get_object('steps').get_object('extracted').get_number('amount') * 1.08); return o.to_clob; end;", "output_key": "converted" }, { "agent_code": "report_agent", "input_mapping": { "amount": "{$.steps.converted.amount_eur}" }, "output_key": "report" } ] }]';
l_workflow_id := uc_ai_agents_api.create_agent( p_code => 'invoice_pipeline', p_description => 'Extract, convert currency in PL/SQL, then report', p_agent_type => uc_ai_agents_api.c_type_workflow, p_workflow_definition => l_workflow_def, p_status => uc_ai_agents_api.c_status_active ); commit;
l_result := uc_ai_agents_api.execute_agent( p_agent_code => 'invoice_pipeline', p_input_parameters => json_object_t('{"document": "Invoice total: 100 USD"}'), p_session_id => uc_ai_agents_api.generate_session_id );END;/Return value → real JSON type. UC AI parses the returned CLOB and stores it under output_key with its real type. A later step can therefore navigate it:
| Snippet returns | Stored as | Referenced by |
|---|---|---|
return '42'; | number 42 | {$.steps.key} |
return o.to_clob; (a JSON object) | object | {$.steps.key.field} |
return 'done'; | string "done" | {$.steps.key} |
UC AI stores text that is not valid JSON as a plain string.
output_key is optional for a PL/SQL step. Omit it for a step with only a side effect, for example a write to a table. The code must still return a value that is not null.
Gating the rest of the workflow. A PL/SQL step uses normal condition gating. It has its own condition, and a later step can gate on its output. A PL/SQL step can also return an object with "__control__": "stop". The workflow then stops at once, and no later step runs:
{ "step_type": "plsql", "plsql_function_call": "declare s json_object_t := json_object_t(:parameters); o json_object_t := json_object_t(); begin if s.get_object('steps').get_object('review').get_number('score') < 3 then o.put('__control__', 'stop'); end if; return o.to_clob; end;", "output_key": "gate"}Input mapping reference
Section titled “Input mapping reference”A string value that resolves a path from the workflow state:
"question": "{$.input.question}"Available paths
Section titled “Available paths”| Path | Description |
|---|---|
{$.input.*} | Original input parameters passed to the workflow |
{$.steps.<output_key>} | Full output of a previous step |
{$.steps.<output_key>.<field>} | One field from the structured output of a step |
Message log at a glance
Section titled “Message log at a glance”A workflow is a deterministic pipeline and not a conversation. The uc_ai_agent_messages transcript therefore holds a single assistant row with the output of the last step. Its agent_code names the workflow agent. The detail of each step lives in uc_ai_agent_executions. Each step runs as a child execution under the workflow wrapper. To see an intermediate step, query that table by parent_execution_id.
| seq | role | agent_code | content |
|---|---|---|---|
| 1 | assistant | haiku_workflow | An improved haiku: ... |
Monitoring running workflows
Section titled “Monitoring running workflows”A workflow writes a checkpoint of its state to uc_ai_agent_executions.current_state after every completed step. A conversation agent and a handoff agent write a checkpoint after every turn or handoff. UC AI commits each checkpoint in an autonomous transaction. You can therefore watch a long workflow from another session:
SELECT status, json_value(current_state, '$._last_completed_step') AS last_step, json_value(current_state, '$._checkpoint_at') AS checkpointed_at FROM uc_ai_agent_executions WHERE id = :execution_id;The checkpoint contains the full workflow state (all step outputs so far) plus two markers:
_last_completed_step— agent code of the most recently completed step_checkpoint_at— timestamp of the checkpoint
After a successful run, UC AI clears the checkpoint. The final state then lives in output_result. A failed or crashed run keeps its last checkpoint. You can therefore see how far the workflow came, and what each step returned. uc_ai_agents_api.get_execution_details also returns current_state when a checkpoint exists.