DEV Community

Angel Fernandez Temiño
Angel Fernandez Temiño

Posted on Originally published at github.com

A planner that cannot invent a capability

RPA projects tend to start the same way. The client sends the process definition: a Word document with a purpose, a table of steps and their business rules, sometimes a PDF, sometimes a spreadsheet, sometimes all three. The first days go into finding out which of those steps the estate already covers, which do not exist yet, and writing the design.

A model could draft that design in a minute. The reason nobody lets it is that it will invent a workflow that sounds right. ArchiveInvoicePDF reads like something the estate has. It does not, and you find out in week three.

My day job is building GenAI platforms that run in production, and the rule there is the one you apply to any distributed system: never act on an output you cannot check. So I built the planner the other way round. The model proposes; a deterministic resolver checks every step against a catalog of the functions that exist, and a step nothing can do is reported as missing. This post walks through it on the smallest example in the repository, with the real output.

The catalog

Before planning anything, the sources are read into a catalog. FlowGlyph reads UiPath projects, SAP OData $metadata, SAP Cloud Integration iFlows, SAP Build, BPMN, n8n exports and OpenAPI documents with parsers, not with a model, and turns every workflow, service operation and API call into a typed capability: inputs, outputs, what it does to the world, and whether a retry is a repeat.

The example uses a UiPath project and an OpenAPI document, merged into one catalog:

import flowglyph

uipath = flowglyph.extract("tests/fixtures/synthetic/invoices")
api = flowglyph.extract("tests/fixtures/openapi/payments.yaml")
uipath.merge(api).save("examples/invoice-processing/catalog")
Enter fullscreen mode Exit fullscreen mode

The catalog is plain files you can commit: catalog.jsonl, graph.json, a lock file, and one Markdown card per function. Eighteen capabilities in this one: four UiPath workflows, seven API operations, and seven activity types that are documented but cannot run on their own.

The requirements

The requirements file is four sentences, the kind of thing a client writes:

# Supplier invoice intake

When an invoice arrives, check that the vendor exists in our records.

If the amount is above 10,000 EUR, ask the finance approver before going on.

Then create the invoice in the payments system, using the invoice reference so that a retry
never books it twice, and archive the PDF.
Enter fullscreen mode Exit fullscreen mode

A PDF or a Word file works the same way: flowglyph plan reads paragraphs and tables from .pdf and .docx, one table per sheet from .xlsx and .csv, and a whole folder of them in name order. A scanned PDF without a text layer is refused, not guessed.

The plan

flowglyph plan examples/invoice-processing/requirements.md examples/invoice-processing/catalog \
  -o examples/invoice-processing/out
Enter fullscreen mode Exit fullscreen mode

--via picks the model: the Anthropic API by default, or the coding assistant you are already signed in to (claude-code, codex, gemini, cursor), or any OpenAI-compatible server (Ollama, vLLM, LM Studio). The model never sees your sources. It sees the requirements and a shortlist of candidate functions picked by BM25 over the catalog, and it has to answer in a strict JSON Schema.

Three things happen in order:

  1. Shortlist. BM25 over the cards finds the candidates for each sentence of the requirements. The model is not handed eighteen functions here, and it would not be handed three thousand in a real estate either.
  2. Proposal. The model writes workflow.plan.yaml: a goal, typed inputs, steps with an intent, a capability id, arguments bound to inputs or to earlier steps, conditions, and a list of assumptions.
  3. Resolution. The resolver checks every step: the capability id exists in the catalog; every required input of that capability is filled; each argument has the type the input declares; every reference points to an earlier step. Anything that fails is a problem on the step. A step the model could not assign to any candidate is a gap.

Here is the proposal for the booking step. The model picked the API operation over the UiPath workflow, and said why:

- id: create_invoice
  intent: Create the invoice in the payments system, with the invoice reference as idempotency key so
    a retry never books it twice.
  capability: openapi/Payments_API/createInvoice
  arguments:
  - name: Idempotency-Key
    source: workflow_input
    value: invoice_reference
  - name: body
    source: workflow_input
    value: invoice
  after:
  - validate_vendor
  - request_approval
  condition: validate_vendor.out_Exists == true
  join: any
Enter fullscreen mode Exit fullscreen mode
assumptions:
- I used createInvoice from the Payments API instead of uipath/Invoices/Main or BookInvoice, because it
  is the only candidate with key-based idempotency, which a retry-safe booking needs.
- The candidate list has no capability for archiving the PDF, so that step has none assigned.
Enter fullscreen mode Exit fullscreen mode

That second assumption is the whole point. The archive step comes out like this:

- id: archive_pdf
  intent: Archive the invoice PDF once the invoice is created. No candidate capability does this; a document
    archive capability that takes the PDF location (and ideally the invoice reference) is missing.
  capability: null
  arguments: []
Enter fullscreen mode Exit fullscreen mode

And the checked workflow, workflow.ir.yaml, says so at the top:

status: needs attention
problems: []
unresolved:
- archive_pdf
warnings:
- 'request_approval has side effects: run it with an idempotency key so retries are safe'
Enter fullscreen mode Exit fullscreen mode

No problems: every resolved step exists, is fully bound and typed. One unresolved step: nothing in the sources archives a PDF. One warning: the approval step changes something and nothing guarantees a retry is safe, so run it with a key. The plan is the design, the gap list and the first line of the estimate, on day one, in the client's own words.

The same thing from the Word document

In the README the same planner reads a two-page Word process definition with a four-row steps table. Two of the client's steps come back as missing:

Book emailed supplier invoices without creating duplicates, then archive the PDF
 Step               Capability                             Intent
 validate_vendor    uipath/Invoices/Steps/ValidateVendor   Check that the vendor on the invoice exists in our records.
 notify_purchasing  missing                                Notify purchasing that the vendor is unknown and stop processing the invoice.
 request_approval   uipath/Invoices/Steps/RequestApproval  Ask the finance approver for approval before booking a high-value invoice.
 create_invoice     openapi/Payments_API/createInvoice     Create the invoice in the payments system, keyed by the invoice reference so a retry never books it twice.
 archive_pdf        missing                                Archive the invoice PDF in the document store with a 10-year retention period.
Enter fullscreen mode Exit fullscreen mode

Nothing notifies purchasing, nothing archives a PDF. Those two go straight into the backlog, and nothing was invented to fill them.

Opening it in Studio

The resolved part does not stay as YAML:

flowglyph compile plan/workflow.ir.yaml --catalog catalog --to xaml -o Main.xaml
Enter fullscreen mode Exit fullscreen mode
  warning conditions are copied as written; rewrite them as Visual Basic expressions in Studio
Compiled plan/book-emailed-supplier-invoices-without-creating-duplicates-then-archive-the-pdf into Main.xaml
Enter fullscreen mode Exit fullscreen mode

Main.xaml is a Flowchart with each resolved step as an InvokeWorkflowFile, its arguments ready to bind, a FlowDecision per condition, and a Comment where each missing step goes:

<ui:Comment DisplayName="Notify purchasing that the vendor is unknown and stop processing the invoice."
            Text="...: no capability does this yet" />
Enter fullscreen mode Exit fullscreen mode

--to bpmn gives the process owner the same workflow as BPMN 2.0 with a diagram. A coding agent with the project's activity cards (another command, flowglyph context, writes them from the project) builds the two missing steps from there.

What it does not do

It does not judge whether a resolved step is the right one, only that it exists, fits and is typed. The model chose createInvoice over BookInvoice for a good reason and said so, but a worse choice that still type-checks would pass. Read the assumptions.

Conditions are copied as written. The resolver checks that they reference existing steps and inputs; it does not evaluate them, and the XAML compiler tells you to rewrite them as Visual Basic.

It needs a model, any of the ones above, and the quality of the proposal is the model's. What the resolver guarantees is narrower and, I think, more useful: nothing in the output exists only in the model's head.

And it is alpha. The IR is at 0.4 and may change; the adapters have run on vendor templates and synthetic projects, not on a real client estate yet.

Try it

The quickest test needs no clone and no model. It reads UiPath's own template and tells you which workflows are safe to run twice:

pip install "flowglyph[cli]"
flowglyph idempotency https://github.com/UiPath/ReFrameWork --kind workflow
Enter fullscreen mode Exit fullscreen mode

Then the planner on your own sources:

pip install "flowglyph[all]"
flowglyph extract ./your-automations -o catalog
flowglyph plan requirements.md catalog -o plan --via claude-code
Enter fullscreen mode Exit fullscreen mode

The code, the examples and every output above are at github.com/afernandeztem/FlowGlyph, Apache-2.0.

Help shape it

The most useful thing you can send is a file it gets wrong: an issue with the XAML, the EDMX or the BPMN that broke. Use cases I have not thought of go to the repo's Discussions. And there are six good first issues, from a Power Automate adapter to an n8n example.

What I would like to know from people who have put a model in front of a real estate: how do you keep it from inventing functions, and does a gap list on day one change how you scope the work?

Top comments (0)