Skip to content

Repository files navigation

CUGA FLO

CUGA FLO

CUGA FLO (FLow Oversight) is a process harness for policy-aware, structurally-enforced agent workflows. The architecture separates deterministic process execution from agentic reasoning and governance. CUGA FLO integrates with any workflow engine via MCP; three are currently supported — LangGraph (in-process), Flowable, and Apache KIE (Kogito) — selected per application, with any other engine pluggable through the same interface. LLM reasoning is scoped to designated control points — task fulfillment, gateway routing, and hook-governed flow adaptations.


Overview

CUGA FLO combines the deterministic structural guarantees of workflow engines with open-world, policy-bounded agentic reasoning without relinquishing process semantics to the LLM layer. Rather than allowing agents to bypass or redefine process behavior, adaptations—when permitted—must be explicitly enabled and remain policy-adherent within governed execution boundaries. Existing systems tend to choose one side of the tradeoff: classical BPM emphasizes structural conformance but limited runtime adaptability, whereas LLM-as-planner approaches favor adaptability at the cost of conformance guarantees and auditability. CUGA FLO seeks to reconcile these two extremes by providing a holistic process harness that can be configured to enforce both structural conformance and runtime adaptability.

Within that structure, three layers of policy-aware reasoning operate:

  • FlowAgent oversees the process as a whole. At permitted interception points (hooks), it can adapt the flow — skipping nodes, jumping to a target, escalating, or halting — by reasoning against a hook-specific policy and the full current process state.
  • DecisionAgent reasons about gateway routing: given the condition evaluation result, the process state, and the gateway's policy, it selects which branch to follow.
  • TaskAgent fulfils individual tasks: it executes the task logic in accordance with the task's policy and writes results back into the shared process variable namespace.

MCP as the integration bridge. The FlowAgent harness does not execute the BPMN process graph itself at runtime — a WorkflowEngine does. The two are decoupled by MCPFlowBridge, a FastMCP server that mediates all communication between them. The FlowAgent exposes reasoning tools (execute_task, route_gateway, evaluate_hook) over MCP; the ProcessRegistry exposes process metadata tools (register_flow, get_bpmn_process, get_flow_annotations); and the WorkflowEngine exposes run_process. All invocations — including the initial workflow trigger from FlowAgent — go through MCP tool calls, enabling remote or cross-process transport. This makes the execution engine replaceable: LangGraph, Flowable, and Apache KIE (Kogito) each implement the same WorkflowEngine interface today, and any other engine — enterprise-grade or otherwise, with its own persistence, audit trails, and compliance guarantees — connects the same way, with no changes to the FlowAgent harness. See WorkflowEngine below.

This makes CUGA FLO suited for regulated, repeatable, or auditable processes — loan approvals, compliance workflows, onboarding pipelines — where the sequence of steps is structurally enforced but each step and permitted intervention is still governed by policy.


Key Concepts

FlowAgent

The meta-agent and single entry point for the process harness. At initialisation it:

  1. Parses the BPMN 2.0 XML into a BPMNProcess (elements + sequence flows)
  2. Compiles each task, gateway, and hook definition into the corresponding agent/policy structure
  3. Registers its reasoning capabilities (execute_task, route_gateway, evaluate_hook) on the shared MCPFlowBridge

At runtime, the WorkflowEngine drives execution. The FlowAgent responds only at MCP-mediated control points, each call carrying a ControlPointContext that embeds the full process state, execution history, model summary, and task instruction. The FlowAgent manages the shared process variable namespace, evaluates hook policies to decide whether and how to adapt the flow, and builds the final completion message from task results.

A FlowAgent can be registered as a sub-agent inside a CugaSupervisor, where it is treated as a peer alongside CugaAgent instances.

flow_agent = FlowAgent(
    process_key="loan_approval",
    bridge=bridge,                # MCPFlowBridge; created automatically if None
    hooks=[Hook(id="h1", hook_type=HookType.EDGE, location="Flow_id", policy=md)],
    process_variables={"amount": 0, "approved": False},
)

Alternatively, load from a YAML config file:

from cuga_flo.engine.flow_config import load_flow_from_yaml

flow_agent = load_flow_from_yaml("config/process_config.yaml")

See FlowAgent vs CugaSupervisor for how it differs from a CugaSupervisor.


TaskAgent

Wraps a CugaAgent for execution inside a single BPMN task element, scoping it to a specific task with its own policy. Each task in the diagram is bound to a TaskAgent instance that:

  • Resolves task input from process variables via input mapping
  • Executes the underlying CugaAgent
  • Writes outputs back to process variables via output mapping
  • Records the result in FlowState with status: completed or status: failed
  • Fires optional pre_execute / post_execute hooks
TaskAgent(
    task_id="Activity_0oydey5",
    task_name="Check Credit",
    agent=credit_checker_agent,       # CugaAgent instance
    input_mapping={"applicant": "applicant_name", "amount": "loan_amount"},
    output_mapping={"score": "credit_score"},
)

DecisionAgent

A per-gateway routing agent that binds a CugaAgent to a specific gateway, giving it a routing policy and the responsibility to select the correct outgoing branch. It operates in two distinct steps:

Step 1 — condition evaluation (deterministic, no LLM) The gateway condition expression is evaluated by substituting ${variable} tokens from process variables and applying a binary comparison — without eval(). Produces TRUE, FALSE, or UNKNOWN.

Step 2 — policy-governed decision (CugaAgent) If the condition result is conclusive and unambiguous, the flow is selected directly. Otherwise, the CugaAgent reads the condition result, the full process state, and the gateway's markdown policy, then selects exactly one flow ID in adherence to that policy.

Gateways with a single outgoing flow, or configured as mode: native, are routed inline by FlowAgent using condition evaluation directly — no DecisionAgent is instantiated for them.

gateways:
  Gateway_09ad5fc:
    mode: decision_agent
    condition: "${credit_score} > 0.6"
    policy: "policies/decision-credit_decision.md"
    flows:
      Flow_approve: { decision: "Approve — credit score sufficient" }
      Flow_reject:  { decision: "Reject — credit score insufficient" }

Hook

Hooks are annotations over BPMN sequence flow edges. When execution reaches an annotated transition, CUGA FLO intercepts it and reasons — against the current process state and the hook's policy — about how execution should proceed before the target node is entered. Hooks are declared separately and attached to flows by ID. How a hook is materialised at runtime is engine-specific — see each engine's doc under WorkflowEngine below.

Each hook carries:

Field Purpose
location BPMN flow ID to intercept — exactly one hook per edge
hook_type EDGE (the only type — hooks annotate sequence flow edges)
condition Optional guard — hook is skipped if it returns false
policy Markdown policy; when present, FlowAgent reasons with its LLM against it

The HookResult.action determines what happens next:

Action Effect
CONTINUE Proceed to the target node normally
SKIP_NODE Skip the immediate next node
SKIP_TO Jump directly to a named node, bypassing all intermediate nodes
SWAP_NODES Swap two nodes: redirect to node_b when node_a was next, or node_a when node_b was next
TERMINATE Hard-halt the process immediately
REMOVE_NODE Remove a node from the process topology at runtime: the engine rewires its predecessor and successor flows to bypass it and resumes at the correct point
ADD_NODE Insert a new task node into the process topology at runtime: the engine wires flows through the new node before the current target and resumes at the inserted node

REMOVE_NODE and ADD_NODE trigger a topology modification and may only target nodes that have not yet executed. The engine is responsible for applying the structural change and resuming execution at the correct point without replaying already-executed nodes.

Engine support varies. This table is the full vocabulary the FlowAgent can emit, not a guarantee every engine honours all of it. Each action is realised through whatever the target engine's API exposes (or how far its internal execution model can be extended), so LangGraph, Flowable, and Kogito each support a different subset — the richer structural actions (SWAP_NODES, REMOVE_NODE, ADD_NODE) in particular. Constrain a process to what its engine actually supports via action_permissions; see each engine's doc under WorkflowEngine for exactly how it realises each action.

Hook reasoning is performed by the FlowAgent itself — not a separate agent — because hooks are a process-level concern. The FlowAgent holds the full process state and BPMN structure, and reasons against the hook's policy to decide what flow adaptation (if any) is warranted. Hooks are the only points in the process where the FlowAgent is permitted to deviate from the nominal BPMN path, and every such deviation is policy-governed and recorded in the audit log.

Separation of concerns: CUGA FLO issues the hook action instruction — it does not execute it. Carrying out the action is the responsibility of the workflow engine. The engine receives the HookResult via the MCP bridge and applies the corresponding structural intervention (routing, graph rebuild, halt, etc.) according to its own execution model.

Per-process action_permissions (declared in the YAML config) explicitly list which hook actions are permitted or prohibited for a given process, providing an additional governance layer over what adaptations the FlowAgent may apply.


MCP Bridge

MCPFlowBridge (cuga_flo/mcp/bridge.py) is a FastMCP server that acts as the integration contract between the FlowAgent harness and any WorkflowEngine. The two sides register independently:

FlowAgent side — registers reasoning tools:

MCP Tool Called by engine when
execute_task A BPMN task node is reached
route_gateway A gateway node needs a routing decision
evaluate_hook A hook intercept point fires

ProcessRegistry side — registers process metadata tools:

MCP Tool Purpose
register_flow Parse YAML + BPMN and cache the process definition
get_bpmn_process Fetch the serialised BPMNProcess by key
get_flow_annotations Fetch engine-consumable config (task IDs, hooks, conditions, permissions)

WorkflowEngine side — registers:

MCP Tool Called by FlowAgent when
run_process Starting a new process instance

Every MCP call carries a ControlPointContext — a dataclass embedding the full process state, execution history, model summary, and task instruction — so each reasoning call is self-contained and stateless from the engine's perspective.

from cuga_flo.mcp.bridge import MCPFlowBridge

bridge = MCPFlowBridge()
bridge.register_registry(registry)       # exposes register_flow, get_bpmn_process, get_flow_annotations
bridge.register_flow_agent(flow_agent)   # exposes execute_task, route_gateway, evaluate_hook
bridge.register_engine(engine)           # exposes run_process

# FlowAgent calls run_process via an in-process MCP client; swappable for HTTP/SSE transport
client = bridge.get_client()

A remote transport (HTTP/SSE) can be substituted without changing any FlowAgent or engine logic — enabling cross-process or cross-host deployment. Which transport an engine actually uses is engine-specific — see each engine's doc under WorkflowEngine below.


WorkflowEngine

WorkflowEngine (workflow_engine.py) is the abstract execution backend. It holds the process model and instance state, drives execution node-by-node, and communicates with the FlowAgent exclusively through the MCP bridge. The single abstract method is:

async def _run_via_mcp(
    self,
    process: BPMNProcess,
    initial_inputs: dict,
    mcp_server: MCPFlowBridge,
) -> FlowState:
    ...

At each control point, a WorkflowEngine calls the corresponding FlowAgent MCP tool with a ControlPointContext; any engine implementing the interface plugs in with no changes to the FlowAgent harness. Three currently do, selected per application via workflow_engine: {type: ...} in its config YAML. They differ in what each engine's own API exposes — and how far its execution model can be extended — to realize task, gateway, and hook control points, but the FlowAgent, the MCP bridge, and the YAML/policy authoring surface are identical across all three. See cuga-flo-workflow-engines.md for the architecture shared by all three.


LangGraph

type: langgraph (the default) — runs in-process, no external service. LangGraph owns the compiled process graph and its execution; CUGA FLO contributes LLM reasoning at each control point (task, gateway, hook) through the same MCP bridge interface.

See README-LANGGRAPH.md for the full description of:

  • How LangGraphWorkflowEngine compiles a BPMNProcess into a LangGraph StateGraph, and how MCP-backed handlers are wired into each node
  • How the structural hook actions (REMOVE_NODE, ADD_NODE) are realised as a live graph recompile

Flowable

type: flowable — runs alongside Flowable as an external workflow engine. Flowable owns process state, persistence, and token routing; CUGA FLO contributes LLM reasoning at each control point (task, gateway, hook) through the same MCP bridge interface.

See README-FLOWABLE.md for the full description of:

  • The two components that enable the integration: the FlowableProxy (REST client mediating communication with Flowable) and the augmented BPMN model (the Flowable-deployed process file extended with callbacks to CUGA FLO and hook-action handling)
  • The three BPMN extensions required for each control-point type: task agent (ScriptTask), decision agent (ScriptTask + adapted gateway), and hook (ScriptTask + boundary event + Task_DynamicSkip)

Apache KIE (Kogito)

type: kogito — runs against Apache KIE (Kogito). Kogito owns process execution and state, compiled into a Quarkus service at build time; CUGA FLO contributes LLM reasoning at each control point (task, gateway, hook) through the same MCP bridge interface.

See README-KOGITO.md for the full description of:

  • The app lifecycle: apps authored under applications/<app-name>/, turned into a runnable service by scripts/build_kogito_app.sh <app-name>
  • How the hook mechanism differs from Flowable's — one script task, no boundary event, no shared Task_DynamicSkip — plus the components (KogitoProxy, the CugaFlo / FlowRedirect Java runtime) and known gaps

ProcessRegistry

ProcessRegistry (process_registry.py) is a catalog of BPMN process definitions. It maps short process keys to ProcessDefinition objects (pairing a parsed BPMNProcess with a FlowConfig) and caches parsed results for reuse across invocations.

register_from_directory() auto-discovers process definitions from a directory using the flow_agent_app_inline/ layout convention: each subdirectory containing a YAML file with a flow: key is registered as a named process.

registry = ProcessRegistry()
registry.register_from_directory("applications/")

# Lookup returns (BPMNProcess, FlowConfig) — cached after first parse
process, config = registry.get("loan_approval")

FlowState

FlowState extends AgentState with process-specific fields:

Field Description
process_variables Shared dict readable and writable by all nodes
execution_path Ordered list of node IDs traversed so far
gateway_decisions Record of each gateway's chosen flow
hook_evaluations Audit log of every hook invocation and its outcome
task_results Dict of task_id → result for all completed tasks

Process variables are the primary inter-node communication channel. Task agents write outputs into them; gateway conditions read from them; hook policies inspect them.


ConditionEvaluator

A module-level utility (eval_condition) that evaluates BPMN condition expressions without eval(). It:

  1. Substitutes ${variable} tokens with values from process_variables
  2. Parses the resulting expression into a binary comparison
  3. Applies the operator safely using Python's operator module

Used by DecisionAgent and by FlowAgent directly for native-mode gateways.


Remote agents over A2A

Any wrapper agent can reach an external agent instead of, or alongside, its local CugaAgent. Declare the agents once, then reference them by name:

remote_agents:
  agent0:
    url: "http://localhost:9000"
    timeout: 90                      # optional; keep under the 120s control-point ceiling
    auth: {type: bearer, token: "…"} # optional

Two bindings, and the difference is where authority sits.

Binding Declared on Effect
Delegation tasks[].agent.agent_type: agent0 Replaces the local CugaAgent. The remote agent performs the work and is the authority for that fulfilment
Consultation gateways.<id>.human_consultation: agent0
hooks[].human_consultation: agent0
Adds a consult_user tool to the local agent. It may ask a person, then still decides itself
tasks:
  - id: "Activity_update"
    mode: task_agent
    agent:
      name: update
      system_instruction: |
        For row $row_key set Adjustment to $new_value.
        user escalation: Which row ($row_key) and value ($new_value)?
      agent_type: agent0             # delegated wholesale

gateways:
  Gateway_next:
    mode: decision_agent
    condition: |
      Choose the outgoing flow based on the user's input.
      user escalation: Ask which of the outgoing flows to take.
    agent_type: cuga_agent           # the decider stays local…
    human_consultation: agent0       # …but may ask a person via agent0

hooks:
  - id: "Flow_check"
    type: edge
    location: "Flow_check"
    instruction: |
      Confirm the adjustment is within tolerance.
      user escalation: Ask whether to proceed given the variance.
    human_consultation: agent0       # per hook — one that needs no human binds nothing

Consultation is declared on the element that reasons, never above it: per gateway (DecisionAgent is built per gateway) and per hook (FlowAgent keeps one hook agent per hook). flow.agent_type remains the FlowAgent's own reasoning agent and is process-wide.

Three things worth knowing before using it:

  • A user escalation: block is a contract. It names parameters the remote agent must obtain from the user. On a delegated task the instruction goes over verbatim, so the remote agent owns that conversation. On a gateway or hook the local LLM reasons from the field and composes the question it passes to the tool.
  • Everything happens inside a blocking control point. On Kogito that is a 120s ceiling (CugaFlo.java); exceeding it fails the process instance with no retry. Fast lookups fit; a long human conversation does not, and needs a modelled BPMN user task instead.
  • Failure is split deliberately. An unreachable delegate target fails loudly — a task that cannot execute is a broken app. An unreachable consultation tool is dropped with a warning and the gateway still routes. An undeclared name fails at config load either way.

Implementation is remote_agent.py (registry, RemoteTaskExecutor, make_consultation_tool) over the A2A client in cuga_supervisor/a2a_protocol.py. Every exchange is recorded on the ActivityTracker, so a consultation that shaped a routing decision appears in the trace rather than only in the remote agent's logs. See .claude/plans/agent0-team-brief.md for what a remote agent must expose.


Demo Apps

Four inline demo processes are included under applications/, each illustrating a different combination of CUGA FLO capabilities and, between them, exercising all three WorkflowEngine backends:

App Engine Description Highlights
loan_approval Flowable Multi-step loan processing with credit check, compliance, and approval gateways Exclusive gateway with agentic routing decision, followed by a policy-governed hook on the outgoing flow
loan_approval_kogito Apache KIE (Kogito) The same process as loan_approval — same BPMN, same policies — running on Kogito instead Same highlights as loan_approval; compares the two engines against an identical process
receive_order LangGraph Order intake flow with inventory check and fulfilment routing Parallel gateway splitting execution across concurrent branches, followed by a hook that intercepts the merge transition
trip_planner LangGraph Travel planning flow with itinerary assembly and booking steps Two TaskAgents: one extracts the planning preference from natural language input, one plans the itinerary; no hooks

Start any demo with:

cuga-flo start <app_name>

# Examples:
cuga-flo start loan_approval
cuga-flo start receive_order
cuga-flo start trip_planner

loan_approval and loan_approval_kogito need their engine running first — see README-FLOWABLE.md / README-KOGITO.md.

Each app directory follows the same layout: a BPMN file, a flow_config.yaml referencing it, agent definitions, and per-task/gateway policy markdown files under policies/.


Module Structure

src/cuga_flo/
├── engine/
│   ├── flow_agent.py          # FlowAgent — process harness meta-agent
│   ├── flow_agent_state.py    # FlowState — process-aware agent state
│   ├── flow_config.py         # FlowConfig — YAML-based instantiation
│   ├── bpmn_parser.py         # BPMN 2.0 XML parser → BPMNProcess
│   ├── task_agent.py          # TaskAgent — CugaAgent wrapper for task nodes
│   ├── decision_agent.py      # DecisionAgent — two-node gateway router
│   ├── hook_manager.py        # Hook, HookManager, HookAction, HookResult
│   ├── remote_agent.py        # A2A delegation / consultation bindings
│   ├── workflow_engine.py     # WorkflowEngine ABC + ControlPointContext
│   ├── langgraph_engine.py    # LangGraphWorkflowEngine — one of three engine adapters
│   └── process_registry.py    # ProcessRegistry — multi-process catalog
├── mcp/bridge.py              # MCPFlowBridge — FastMCP integration contract
├── adapters/flowable/proxy.py # Flowable REST client
├── adapters/kogito/           # KogitoProxy + CugaFlo/FlowRedirect Java runtime
└── cli/                       # `cuga-flo` command-line entry point

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages