Agent guide
This guide is for an agent (Claude Code, Claude Desktop, Codex, Hermes Agent or any MCP client)
that turns what people say about a process into a model, a simulation and a document, with no one
clicking in the app. Every step is an MCP tool of the Lila MCP server (MCP.md, where each tool
has its full contract) or the matching lila command (CLI.md). Steps 2 to 7 below,
one tool call at a time, run in CI with no UI: packages/mcp/test/agent-flow.e2e.test.ts, starting
from the synthetic interview packages/mcp/test/fixtures/entrevista-tarjeta.txt. Step 8 needs the
desktop app, so it is checked by hand with tools/agent-live-check.mjs.
The server is its own npm package, started with npx -y @lila-modeler/mcp; setting it up in each
client is in MCP.md. A client that only has MCP reads this guide as the
resource lila://guide (the formats are lila://outline-format, lila://scenario-format and
lila://scenario-schema), and the prompt interview-to-process starts the flow below with an
interview (MCP.md).
The flow
| Step | Tool | CLI |
|---|---|---|
| 0. The file exists: read what it holds | get_project, get_scenario |
lila project show, lila scenario show |
| 1. Read the interview, write an outline | (the agent) | |
| 2. Create the process | create_process |
lila process create |
| 3. Fix it | edit_process, get_process_outline |
lila process edit, lila process show |
| 4. Document it (optional) | annotate_element, get_raci_matrix |
lila process annotate, lila process raci |
| 5. Fill the scenario | patch_scenario, or export_scenario_template + import_scenario_sheet |
lila scenario template, lila scenario import |
| 6. Simulate and keep the run | run_simulation with saveRun: true |
lila run … --save |
| 7. Hand over | export_document, export_results, export_diagram |
`lila export doc |
| 8. Show it | open the .lila in the app; it reloads on every later write |
open -a "Lila Modeler" file.lila (macOS) |
Two rules shape the flow:
- The outline is the map, the scenario the numbers. Put the lanes, the steps, the order and the
branches in the outline. Times, staff, arrivals and costs are scenario data: you can put
duration,resourcesand branchprobabilityin the outline (they go into the base scenarioas-is.scenario.json), but anything else goes throughpatch_scenarioor a sheet. - Everything is the
.lila. There is no server state. Every tool reads the file, does its work and, if it writes, writes the whole file atomically. The desktop app that has it open reloads it by itself.
0. An existing .lila: get_project
When the .lila already exists, start here, not with the interview:
{ "name": "get_project", "arguments": { "project": "card.lila" } }
It lists the processes (their slug is what process takes when there are several), each one's
scenarios with their revisions and extends, and the saved runs, current or stale. It writes
nothing. get_scenario reads one scenario, as stored or resolved: true, with the validator's
notes; read a scenario with it before you patch it, never with a patch_scenario of only
test. Then pick the step below that the person asked for: a new process (step 2), a fix
(step 3), new numbers (step 5) or a new run (step 6).
1. From the interview to an outline
Read the transcript and write down: the roles that do the work (lanes), each thing someone does
(a step, named verb + object: «Validate documents»), the decisions (xor gateways, with the
odds people give: «one in seven» is 0.15), the waits (timer) and how each path ends. Keep the
interviewee's words for names; the person who reads the document will look for them.
Ask before you guess. A process the person cannot recognise is worse than a question.
2. create_process
{ "name": "create_process", "arguments": { "project": "card.lila", "outline": { … } } }
project that does not exist becomes a new one-process .lila; one that exists gets a new process
(it never replaces one: pick another process slug). The answer carries the outline in normal
form, the slug, warnings and notes. Read notes: they flag models that would hang cases,
such as a parallel join waiting for branches of one exclusive split.
dryRun: true checks and returns everything without writing.
3. edit_process and get_process_outline
To change a process, do not create it again: edit_process applies a list of operations (add,
connect, remove, rename, setType, moveToLane, addLane) all or none, keeps every id and
everything the operations do not touch (documentation, RACI, attributes, other processes). Send it
with dryRun: true first and show the changes to the person when the edit is not trivial.
{ "name": "edit_process", "arguments": { "project": "card.lila", "dryRun": true, "operations": [
{ "op": "add", "step": { "id": "verify", "name": "Verify identity", "duration": "5m" }, "after": "receive" },
{ "op": "rename", "id": "issue", "name": "Issue the card" } ] } }
After any edit the process still simulates. Scenario entries that no longer apply are removed
from every scenario of the process (the whole entry of a removed element, or the fields a retyped
element cannot take, such as resources on a gateway), and each removal is reported in
scenarioRemovals as { scenario, id, removed, entry } with the previous values, so you can put
them back elsewhere with patch_scenario. A dry run returns the same report. Then every scenario is
validated against the edited model; an error it did not have before refuses the edit. notes also
names the flows to patch when an XOR's probabilities no longer add up to 1.
get_process_outline reads any process back as an outline, including one drawn in the app or
imported from Bizagi; warnings says what an outline cannot carry.
4. Annotations and RACI
annotate_element writes the description, the RACI list, the catalog references and the extended
attributes of one element by its BPMN id. get_raci_matrix returns the matrix the document will
print. Both are optional; the document is better with them.
5. The scenario
The base scenario as-is.scenario.json starts with the app's defaults (20 cases, one a minute, a
one-hour run) plus what the outline carried (get_scenario with resolved: true shows it, with
the paths a patch uses). Fill in the rest from the interview with one
patch_scenario (JSON Patch, validated before anything is written):
{ "name": "patch_scenario", "arguments": { "project": "card.lila", "scenario": "as-is", "patch": [
{ "op": "replace", "path": "/run/duration", "value": 28800 },
{ "op": "remove", "path": "/elements/StartEvent/triggerCount" },
{ "op": "replace", "path": "/elements/StartEvent/interTriggerTimer", "value": { "type": "exponential", "mean": 900 } },
{ "op": "replace", "path": "/resources/branch-officer/capacity", "value": 3 },
{ "op": "replace", "path": "/resources/credit-analyst/capacity", "value": 2 },
{ "op": "add", "path": "/elements/issue/processingTime", "value": { "type": "constant", "value": 600 } } ] } }
Scenario times are seconds; the format is SCENARIO_FORMAT.md. Resource keys
are the slugs of their names (Branch officer → branch-officer). A JSON Patch add needs its
parent: when the outline had no resources, the scenario has no /resources yet, so add it whole
({ "op": "add", "path": "/resources", "value": { "branch-officer": { "name": "Branch officer", "capacity": 3 } } }). For a TO-BE, pass saveTo: "to-be-4-officers": the new scenario
inherits from the original and holds only the changed keys.
When the numbers come from people rather than from a transcript, hand them a sheet:
export_scenario_template writes the scenario as an .xlsx (SCENARIO_SHEETS.md),
they fill it in, and import_scenario_sheet applies it (dryRun: true first: changes lists
each field before → after, issues the rows it could not apply).
6. run_simulation with saveRun
{ "name": "run_simulation", "arguments": { "model": "card.lila", "scenario": "as-is", "seed": 42, "replications": 10, "saveRun": true } }
The answer is a summary of the run, a few KB: process, bottlenecks, each resource's utilization,
wait and cost, the elements that wait most (topWaits) and the warnings grouped; detail: "full"
returns the whole RunResult (RESULTS_FORMAT.md), which is large. A second
text block says { "savedRun": { "id", … } }. The run is stored in full in the .lila, exactly as
the app stores it, so the app shows it as the current run and the exports use it. Use a fixed
seed so the person can reproduce the numbers; use compare_scenarios to compare AS-IS and TO-BE
(its summary keeps the process and resource rows and the significant differences). A long run
reports progress per replication and can be cancelled.
7. Exports
export_documentwrites the process document asdocxorhtml: process, lanes, steps with their descriptions and RACI, gateways with their probabilities and the results of the current run. The HTML embeds the diagram as SVG.export_resultswrites the run's tables as.xlsx(or four CSV files).export_diagramreturns or writes the diagram as SVG.
None of them overwrites a file unless overwrite: true, and none ever writes over the project.
8. Open it in the app
Open the .lila in Lila Modeler (double click, «Open», or open -a "Lila Modeler" card.lila on
macOS). From then on, every write by an agent (create_process, edit_process, patch_scenario,
a saved run…) shows up in the open window without a click: the app notices the change on disk and
reloads, keeping the mode, the active process and the scenarios. A new process appears as a new
tab. If the person has unsaved changes, the app does not throw them away: it shows «The file
changed outside Lila» with Reload and Keep mine. tools/agent-live-check.mjs checks this
against the real desktop app.
The outline format
{
"name": "Credit card application",
"lanes": ["Branch officer", "Credit analyst", "Control desk"],
"steps": [
{ "id": "receive", "name": "Receive application", "lane": "Branch officer",
"duration": "triangular(5m, 10m, 20m)", "resources": ["Branch officer"] },
{ "id": "validate", "name": "Validate documents", "duration": "normal(8m, 2m)", "resources": ["Branch officer"] },
{ "id": "complete", "type": "xor", "name": "Documents complete?",
"branches": [ { "label": "Yes", "to": "bureau" }, { "label": "No", "to": "missing", "probability": 0.15 } ] },
{ "id": "missing", "name": "Request missing documents", "duration": "5m", "next": "validate" },
{ "id": "bureau", "type": "serviceTask", "name": "Query credit bureau", "lane": "Credit analyst",
"duration": "exponential(mean=3m)" },
{ "id": "assess", "name": "Assess ability to pay", "duration": "normal(25m, 8m)", "resources": ["Credit analyst"] },
{ "id": "approved", "type": "xor", "name": "Approved?",
"branches": [ { "label": "Yes", "to": "issue" }, { "label": "No", "to": "reject", "probability": 0.35 } ] },
{ "id": "issue", "name": "Issue card", "lane": "Control desk", "resources": ["Control desk"], "end": true },
{ "id": "reject", "name": "Notify rejection", "lane": "Branch officer", "end": true }
]
}
| Field | Meaning |
|---|---|
name |
The process name (also the default slug). |
lanes |
Lane names in order. Optional: without it, lanes are collected from the steps. |
steps[].id |
The BPMN id. Scenarios key on it, so pick short stable ids. |
steps[].name |
The label. |
steps[].type |
task (default), userTask, serviceTask, callActivity, xor, and, or, timer, subprocess. |
steps[].lane |
Defaults to the previous step's lane. |
steps[].next |
The step(s) after this one when it is not simply the next in the list (a loop back, a join). |
steps[].branches |
Gateways only: { label?, to, probability? } or { label?, end: true, probability? }. XOR branches without a probability share what is left of 1. |
steps[].end |
This step ends the process (an end event is added after it). |
steps[].duration |
Seconds, 20m, 1h30m, normal(20m, 5m), triangular(1m, 2m, 5m), exponential(mean=4m), or a distribution object in seconds. |
steps[].resources |
Names, or { name, quantity }. With several, selection: "or" takes any one of them. |
The order of the list is the flow. Start and end events are added; ids such as StartEvent,
EndEvent_<step> and Flow_<from>_<to> are Lila's. get_process_outline returns the same
format in normal form (every lane named, durations as objects, probabilities filled in), so an
outline round-trips. The full rules are in MCP.md.
When a call fails
- Every problem at once, with its path, in one pass. A bad call answers
isError: truewith all its problems together — malformed fields and semantic ones alike, in the call's language — each with where it is. An outline:steps[2].branches[0].to: …,steps[0].duraton: unknown field "duraton".An edit names its operation:operations[2].after: …,operations[3].nombre: unknown field "nombre"., and a model the edit would break answers with the validator's code:operations[0].bpmn.Process_1: E-SIN-START: …. Fix all of those entries and call again once; nothing was written. dryRunbefore writing whenever a human should see the change first:create_process,edit_process,annotate_elementandimport_scenario_sheettake it, check everything and write nothing.- Validation is not an error of the tool.
validate_bpmnanswersisError: falsewith a report whoseerrors[]may be full; read them. - Language. Messages follow
lila mcp --lang es(orLILA_LANG), orlocaleper call. Codes (E-…), ids and JSON keys never change.
More than one writer
Agents, the CLI and the desktop app can work on the same .lila:
- Atomic writes. Each write goes to a temporary file that replaces the
.lilawith a rename. A reader never sees half a file. - A lock. Writers take turns through
<file>.lila.locknext to the file. When another writer holds it for more than 3 seconds, the call fails withE-ARCHIVO-OCUPADO(«another program is saving … right now; nothing was written. Try again in a moment.»). A lock untouched for 10 seconds is treated as left by a crash and removed. - Changed on disk. If the file changed between the tool's read and its write, nothing is written and the call fails («… changed on disk while this call was working on it; nothing was written. Try again.»). Call it again: it reads the new file. A saved run is the exception: it is appended to what is there, unless the model or the scenario it ran on changed (then it is already stale and is not saved).
- The app. It reloads after your writes. If the person has unsaved edits, it asks first:
Reload or Keep mine. After Keep mine, saving is refused with
E-CAMBIO-EXTERNO(«… changed outside Lila since it was opened, so nothing was saved…»), so your work is not overwritten; they reload, or use Save As to keep their version in another file.
Do one write at a time per file and wait for its answer; there is no benefit in parallel writes to
the same .lila.
Known limits
- The Word document has no diagram and neither document has the run's charts: Word needs a
PNG and the engine has no rasteriser. The HTML document has the diagram (SVG).
notesin the answer says what was left out. To give someone a picture, send the HTML orexport_diagram. - Layout.
create_processandedit_process(by default) lay the process out withbpmn-auto-layout1.3 plus Lila's lane fix. It is readable, not beautiful: lanes can be taller than needed, a gateway's name can sit on a flow leaving from below, crossings are not untangled, and a sub-process is created collapsed.edit_processwithlayout: truereplaces positions set by hand in that process;layout: falsekeeps them and places new steps simply. Move things in the app when the picture matters. - Edits reach only the main process of a BPMN (the one the simulator reads), and lanes cannot be removed.
- Scenario fields from the outline are only
duration,resourcesand branchprobability. Arrivals, calendars, costs and capacities go throughpatch_scenarioor a sheet. - A long run takes its time. The server keeps answering and reports each replication, and a cancelled call stops the run, but the run itself is not faster. Keep replications reasonable while iterating.