Overview
Agent Flows let you compose typed nodes into a flow on a visual canvas. When you publish a flow, Experio compiles it to a LangGraph graph and runs it — orchestrating LLM calls, knowledge graph queries, MCP tools, document generation, and human approvals as a single pipeline. A flow can run on its own from the canvas, or be enabled as a sub-agent that the chat agent launches on a user’s behalf. Navigate to Admin > AI & Agents > Agent Flows.Agent Flows are distinct from Flows, which orchestrate data-processing jobs (reader, ingestion, enrichment). Agent Flows orchestrate AI agent behavior and compile to LangGraph.
The Flows List
The Agent Flows page lists every flow with its name, description, tags, and latest published version. A Sub-agent badge column shows whether a flow is exposed to the chat agent, and a filter above the list switches between All flows and Sub-agent enabled. From here you can:- Create a new empty flow (give it a name — the system handles the internal id)
- Open a flow to edit it on the canvas
- Duplicate a template into a new draft (see Templates)
- Delete a flow
Building a Flow
Opening a flow shows the canvas editor. You build a flow by dragging nodes from the palette onto the canvas. Nodes are compact cards — they don’t show input/output ports on the canvas. To connect two nodes, drag from one node onto another; the canvas auto-wires their compatible ports. Edges float between nodes and re-route automatically as you move nodes around. The flow name is editable inline at the top of the editor. A collapsible right-hand pane holds the support panels; it re-opens automatically when you click a node:- Inspect — configure the selected node (its config fields, input bindings, and budget), or the selected edge’s routing. Each node also takes an optional Display name — the human-readable step name end users see in the chat progress view while the flow runs (it falls back to the node’s label or id)
- Run — start and watch a run (see Running a Flow)
- Versions — review prior published versions and control which one is Live (see Publishing and Versioning)
Config fields
The Inspect panel renders each node’s config from its schema:- Fields that hold prose — Query, Custom instructions, Prompt, System prompt, Output instructions and the like — render as multi-line text areas you can scroll and resize, rather than a single-line box you have to scrub through. Short identifiers (titles, artifact keys, numeric limits) stay single-line.
- Fields that point at another record render as a picker rather than asking you to paste an identifier:
- Model (
model_id,fallback_model_id,report_model_id) — the configured chat models, with Default model first. See Model Configurations. - Assistant — whose per-assistant model configuration the node borrows. Leave it on Default models to use the global ones.
- MCP server — the server whose tools the step may call. See MCP Servers.
- Document template — the branding template a generated file is rendered into. See Document Templates.
- Model (
- Fields with a fixed set of values — the document Theme, output modes, error policies — render as dropdowns.
- Fields that hold a list — Tool groups, Allowed blocks, an MCP Tool whitelist — render as checklists of the real choices. Leaving one empty is meaningful and the panel says what it means (for a tool whitelist, “all discovered tools”).
- A nested configuration (such as the optional MCP enrichment on the research agent) renders as its own small group of controls, including the pickers above.
The pickers read admin lists. An author without permission to read one still sees that field as a plain text box and can edit the value by hand — the rest of the form is unaffected. If a flow holds a value that is no longer in the list, the field flags it rather than quietly showing “none”.
Bindings
Each node has typed input and output ports under the hood. Connecting two nodes auto-wires their compatible ports; to send one specific value, drag from a node’s output port to a target input in the Inspect panel. A binding path looks like$.nodes.<node_id>.outputs.<port>, and inputs can also bind to run parameters ($.params.*), the chat message ($.params.message), and uploaded files ($.files.*). Bindings are validated when you save — type mismatches and missing required inputs are surfaced inline on the canvas.
Edges order execution; bindings move the data. Reads count too: reading another node’s output — through a binding, or through a {{ nodes.<node_id>.outputs.<port> }} template in one of the node’s config fields — counts as a dependency, not just the edges you drew. That is a safety net against a node running before the value it reads exists, not a guarantee of order. A node reading its own previous output (a loop body) is not treated as waiting on itself. If a node it reads from fails, the reading node fails too rather than running on an empty input — the branch stops there instead of producing a document out of nothing. An input that carries a default is the exception: it falls back to that default and the reading node still runs — but only because it reads the failed step, not because it follows it. A step you drew an edge from is on the failed branch, and that branch stops at the failure whatever its inputs default to.
Drawing the edge is what actually orders the two steps. Validation does check for a node reading the output of a node that isn’t upstream of it, and reports it as a warning: run Validate — or apply an edited definition from Edit as JSON — and every such finding is listed in an amber strip above the canvas, naming the node to fix. Warnings never block publishing, by design, and there is no per-node marker on the canvas or in the Inspect panel, so that strip is the only place you can read the detail. Publish without validating first and nothing surfaces at publish time — the publish dialog lists blocking errors only; the new version’s entry in the Versions tab carries a warning count instead, as a prompt to go back and run Validate.
Declaring run parameters
A flow can carry an optional top-levelparams declaration — a list of {name, description} entries naming the run parameters ($.params.<name>) it expects a launch to supply, beyond the implicit chat message. It’s descriptive rather than enforced at run time (an undeclared param a definition reads still resolves to an empty string, with no error), but it documents what a run needs, and it’s what the hollow-pattern lints check a proposal against. Edit it from Edit as JSON, alongside the flow’s nodes and edges.
Edit as JSON
The canvas toolbar includes an Edit as JSON action. It opens the flow’s full definition as JSON, where you can export it (copy or download) or paste an edited definition back. Applying a pasted definition runs the same server-side validation as the Validate button — unparseable or structurally-invalid JSON (missingnodes/edges arrays) is rejected before it touches the canvas.
Hollow-pattern checks
Structural validation confirms a definition compiles; it doesn’t confirm the flow produces anything. A handful of shapes compile cleanly, run without error, and still deliver nothing — an unrouted loop that silently ends the run when it exhausts its budget, an exit with no result requirement that’s reachable without ever running its producer, an extraction schema too loose for the model to fill in correctly. Save-time validation now also runs a fixed set of hollow-pattern lints for exactly these shapes:
Each lint’s severity depends on who’s authoring: for a person on the canvas, every lint but L8 is a warning (nothing here blocks publishing, and every already-published flow keeps working) — L8 is off, since no published flow predates the
params block. For a proposal from the Admin Copilot, every lint is an error: an agent can’t come back and open the hollow document afterwards, so the shape is refused before the proposal ever reaches your card.
You can run these checks without saving, from three places: the canvas Validate button, POST /api/agent-flows/validate/ (validates a definition sent in the request body against either profile — see REST API), and the validate_agentflow_definition management command (pipenv run python manage.py validate_agentflow_definition <file.json> --profile ai) for checking a definition on disk.
Node palette
The Knowledge Graph Query and Knowledge Graph data nodes share an
on_clarify policy that controls what happens when scope needs clarification: fail the node, pause for human input (interrupt), or continue with empty rows. To get a cited narrative from a data-only Knowledge Graph data node, wire it into a Knowledge Graph Display node (kg.data → kg.display) and bind end.result to the Display node’s text output — its evidence citations render as source chips in the run output.The Read conversation node only works on a run launched from a conversation. A canvas or API run has no conversation to read, so the node fails and says so rather than handing an empty transcript to the rest of the flow, and it only ever reads a conversation the person who started the run can open — their own, or one shared with them — and fails with an authorization error on anything else rather than reading it. Its config sets how many of the most recent messages to read (Messages to read, default 20, returned oldest-first), whose messages to include (all, only the user’s, or only the assistant’s), a per-message character cap (default 40,000 characters; 0 disables truncation), and whether an empty conversation fails the node — left on by default, so a downstream step never quietly drafts a document out of an empty transcript.
The Generate DOCX and Generate PPTX nodes each expose a Document Template selector — pick a registered template by name (managed under Document Templates) to brand the output. The Generate file node adds a format chooser (Word, PowerPoint, PDF, or HTML); the template selector is shown only for the
docx and pptx formats. Markdown in generated content (## headings, **bold**, bullets) is rendered as real Word/PPTX styling instead of literal markers.Budgets
Agent nodes (orchestrator, knowledge research, web research, MCP action) run a model-driven loop and must be bounded. Each exposes a budget so a single node can never run unchecked:
Each box shows that node’s own default — leave it empty to use it. Reaching the call or token limits ends the agent’s loop and returns what it has so far; it does not fail the run, so a budget set too low shows up as truncated work rather than an error.
Max wall seconds is a hard deadline rather than a soft one. The knowledge research, web research and Orchestrator agent steps stop at it and hand on the work they already have; the MCP action step reports a timeout through its own error policy. Every other step that has a time limit is failed when it overruns: the step is cut off, whatever it had produced by then is discarded, and the failure is reported like any other step error, so an unresponsive step can no longer hold a run open indefinitely.
Steps that carry a built-in time limit — the knowledge graph steps, for example — are now actually held to it; a step with no time limit of its own and no built-in default still runs unbounded. Those built-in limits also apply to flow versions you published before the limit existed — a knowledge-graph step that used to take as long as it needed is now failed at its cap. Raise a step’s Max wall seconds if it legitimately needs longer — from the Budget editor on an agent node, or through Edit as JSON (
budget.max_wall_seconds on the node) for a knowledge-graph step, whose Budget editor the canvas doesn’t render.
Publishing and Versioning
A flow you are editing is a draft. Publishing snapshots the current draft into an immutable version:- Click Publish. The draft is validated first — publishing is blocked if validation reports errors. Validation also checks MCP action nodes: an unknown or disabled MCP server, or Allow writes enabled on a server that doesn’t support writes, blocks publishing; whitelisted write tools that would be stripped because Allow writes is off are surfaced as a warning. A Join is checked against the map body it collects: a Body output port set explicitly to a port that body does not produce blocks publishing, because every branch would then count as failed, and a Minimum body length set on a Join whose body produces no default
textport blocks publishing too, because the floor could never fire on any branch. Both are errors rather than warnings, and both are fixable only in the draft — a published version is immutable. - A new
AgentFlowVersionis created with an incrementing version number. Published versions are immutable. - The flow’s latest published version pointer advances to the new version.
The live version
Every run of a flow uses its live version — the version the chat agent launches, and the default for a canvas or API run. The Versions panel marks the current one with a Live badge. By default a flow follows the latest published version: each time you publish, the newest version automatically becomes live. To roll back or hold on an older version, click Set as live on any validated version in the panel — this pins the live version there, and publishing no longer changes what’s live. Click Follow latest to clear the pin and return to auto-advancing on publish. Either action also loads the now-live version onto the canvas, so the editor shows what actually runs — it arrives as an unsaved change, and your saved draft is untouched until you save. If your canvas already holds unsaved changes, you are asked first: loading replaces them and there is no undo, so you can keep editing instead. The pin is applied either way — declining only leaves the canvas alone. While live is pinned to something other than the latest published version, the editor header carries a Live badge naming the pinned version, as a reminder that the newest published version is not the one running. Only a version that passes validation can be set as live. Existing runs always stay on the version they started with; changing the live version only affects new runs. You can review prior versions from the Versions panel. Use Validate at any time to run save-time validation on the draft without publishing.AI provenance and publish warnings
When you apply one or more proposals from the Admin Copilot, the draft carries that forward as provenance — which proposals were applied, and the model, brief, and rule-set version behind each one. A draft with any AI-origin content shows a compact AI-drafted badge in the editor; hand-editing the flow further doesn’t clear it, since the AI content is still there until you change it. Publishing an AI-origin draft re-validates it under the stricterai profile — the same one the copilot’s own proposals must pass — before you ever see the Publish button, and surfaces any AI-profile warnings that turn up under that profile but not under the normal one you publish with. The publish dialog shows the most recent proposal’s model, rule-set hash, and brief, lists those warnings, and requires you to explicitly acknowledge them (a checkbox, not a default) before Publish is enabled — a hand-made draft skips this entirely. This is a warning gate, not a blocking one: publishing itself still runs under the normal human profile, so an AI-profile-only warning doesn’t stop you, it just makes sure you’ve seen it.
Letting the chat agent run a flow
You don’t wire a flow to a dedicated assistant. Instead, you let the normal chat agent discover and launch it as a background sub-agent:- Open the flow and turn on the Sub-agent toggle in the editor header. The flow becomes available to the chat agent.
- Write a clear flow description in the editor next to the toggle. The chat agent reads this description to decide when to run the flow, so describe what the flow does and the kind of request it handles. A sub-agent flow with no description shows an amber nudge — the agent can’t route to a flow it can’t recognize.
$.params.<name> / {{ params.<name> }}, and $.params.message is typically the user’s core request forwarded at launch) and binds the conversation’s uploaded files to the flow’s file slots.
File slots the agent doesn’t fill explicitly fall back to what the user actually attached in that conversation, newest first — a single slot takes the most recent upload; several slots are matched by label and order. So “here’s the RFP, draft a response” fills the flow’s rfp_document slot from the attachment on that very message. Only user uploads are eligible for this fallback: a document produced by an earlier run in the same conversation is bindable when something names it, but is never picked up implicitly as the next run’s input.
If a required file slot is still unfilled, no run is created, and a message is posted into the conversation naming each missing input by its readable label (plus its description, and whether it takes more than one file) and asking the user to attach it and send again. That message is written by the platform, not left to the model to relay, so the user is always told why the flow didn’t start — it is posted at most once per turn.
While a sub-agent flow runs, a live flow-run view opens in the chat’s right-hand panel. It is deliberately minimal: a single status line naming the step in flight (“Working on Draft the response”, by the step’s Display name) and a Stop control; the conversation pulses in the sidebar until the run finishes. Closing the panel leaves a View progress control (above the message box) to reopen it while the run is still going. When the flow pauses for a human, the panel shows the review card there instead — see the note below. When the run finishes the panel closes itself — unless the user has a file the run produced open in the panel’s viewer, in which case it stays until they close that document. There is no result view: the flow posts a message into the conversation with the full result and any files it produced, and that message arrives on its own (no manual refresh), so a panel could only duplicate it. Users can also just ask the agent for the status or results of a flow it launched — including questions about a document the run produced. The agent is not limited to the excerpt of that document carried automatically in its context: it can list the documents produced in the conversation, search inside one for a phrase, and read any part of it on demand, so a question about a passage buried deep in a long report is answerable. Each document is access-checked for the person asking. The full technical detail of any run — step log, per-node outputs, state and token usage — lives in the flow editor’s Run panel, where a run links back to the conversation it ran in.
A paused sub-agent run surfaces an Awaiting review card in the conversation’s live flow-run panel, where the user can approve or reject it (with a comment) — or edit what they were shown and approve that instead — without leaving chat; the panel’s reopen control turns amber (“Review needed”) and the conversation’s sidebar row shows an amber review badge until the pause is resolved. The same pending review is also listed in the Agent Inbox (Admin > AI & Agents > Agent Inbox) for a reviewer to act on, but the conversation is a complete path in its own right — a reviewer never has to go to the inbox. Either path resumes the flow, which then posts both the decision and the run’s outcome back to the conversation. See Human-in-the-Loop.
Running a Flow
From the canvas
Use the run panel in the editor to start a run with parameters and uploaded files. The run executes in the background — progress, per-node status, and produced artifacts stream back to the panel. The usage panel shows per-node model/tool call counts and token usage — including, for MCP action nodes, the names of the tools the run actually called, so you can confirm it used the tool you intended, and, for any node, which model spent that node’s tokens, with its call count and token total. A node’s totals on their own cannot be attributed to either.From chat
When a flow is Sub-agent enabled, the chat agent can launch it during a normal conversation — see Letting the chat agent run a flow. The agent confirms, runs the flow in the background, maps the user’s request onto the flow’s parameters, binds uploaded files to the flow’s input slots, and posts the result and any produced files back into the conversation when the run finishes.Asking to continue an earlier run
No run can be continued once it has ended — including one that finished successfully. Running the flow again starts a new run from the first step: nothing carries over, no partial output is kept, the new run does not read or revise the earlier run’s output, and every approval is asked again. So when a user refers back to an earlier run — “continue the last RFP”, “pick up where it stopped”, “what happened to that one” — the agent looks that run up before doing anything else. If it is still going, the agent reports its state instead of launching a second one. If it has ended, the agent says how — failed (naming the step it stopped at, where there is one), cancelled, or completed — and says plainly that what it is starting now is a fresh run, rather than describing it as a continuation. The completed case matters as much as the failed one: asked to “continue” a run that had finished, the agent was observed announcing a new run as producing an updated document, which is the same defect one status over. The lookup only ever returns the asking user’s own runs in that conversation.Reading a run’s progress
The two surfaces show a run at very different resolutions, on purpose. The editor’s Run panel is the debug surface: it lists the raw step log — one entry per node execution, branch suffixes and all — alongside per-node outputs, the run’s state and its token usage. A Map/Join fan-out appears there as it actually executed, one entry per branch, so “which of the nine sections failed?” is answerable. The chat flow-run panel shows only the step in flight, by name, while the run is going. A fan-out is named by the plan node driving it rather than by branch. It reports nothing about a finished run — it closes itself instead — because the run’s result and its files are posted into the conversation as messages. The single exception is a document the user already has open in the panel’s viewer: a run that finishes mid-read never yanks the file away, so the close waits until they close the document.When a run’s executor stops
A run executes inside the API process; there is no worker queue behind it. If that process stops mid-run — a deploy, a pod restart, a dev-server reload — nothing is left to finish the run. Such runs are reconciled — on the next API start, or on demand from Startup Health — and marked Failed with “Run was interrupted — its executor stopped before the run finished”, so a conversation stops looking permanently busy and its re-open control stops offering a run that nothing will ever complete. A run parked at a Human review gate is never reconciled this way — it is waiting for a reviewer, not orphaned, and resumes as soon as someone answers it. Operators do not have to wait for a restart: Admin > Monitoring > Startup Health has an Orphaned agent flow runs check that reports how many stale runs exist and repairs them in place.Transient errors and retried steps
A network-level failure inside a node — a dropped connection, a read timeout — is retried once after a short pause, so a single blip does not have to end the step that hit it. The retry is spent inside the node’s own budget: a node that has already used up its declared wall budget is not retried. An attempt the retry absorbed is recorded against that node in the run’s step log, so a retry that worked leaves a trace rather than disappearing. A tool call the agent itself can fix is not a failure. Inside Knowledge research agent and Web research agent, a tool call the far end rejected — a Cypher query the graph refused to parse, a type mismatch in a predicate, a filter naming a document type this deployment does not have — is handed back to the agent as a failed tool result with the reason, and the loop carries on: the agent rewrites the query and calls again. Only the failure it cannot act on ends the step: the graph being unreachable, a timeout, or a permission error on the connection itself (the graph answeringNOPERM, which means the credentials or the ACL are wrong and no rewrite will clear it). Before this, one malformed query aborted the whole agent loop and the map branch it ran in — and because the document at the end of the flow does not depend on that branch, the run failed while still producing a document that simply had that branch’s research missing from it. A rejected call is still recorded against the step in the run’s step log, so a rewrite that worked leaves a trace.
The agent steps are never retried, in any pass. Orchestrator agent, Knowledge research agent, Web research agent and MCP action each run a loop that can act on an outside system — send the mail, create the record, write through a connected integration. A retry restarts that loop from its goal, with no memory of what the failed attempt had already done, so an action that succeeded before the connection dropped would simply be performed a second time. These steps fail on the first blip instead, which is what they did before the retry existed. What that buys is worth stating exactly: it stops a whole agent loop being replayed, and nothing more. It does not make a step that writes exactly-once — inside the loop a tool call that failed is still retried there, and a call that failed after the far end had already committed looks no different from one that never landed. A flow that writes through an integration still depends on that integration to tolerate the same request twice.
Nothing is retried in a pass that resumed from a human gate. Re-running a step after a resume could re-ask a reviewer who has already answered, so the exclusion is deliberately blunt: it covers the whole resumed pass, not just the node holding the gate. What the retry actually covers, then, is the non-agent steps that run before a flow’s first gate — a run that never pauses is covered end to end, and a run that pauses is covered only up to that point. Read your own flow’s shape before relying on it: everything past the first gate is uncovered, so a gate drawn early leaves almost the whole run outside the retry. In the built-in RFP Response Pipeline, Review the compliance matrix sits ahead of the section fan-out, so the parallel section authoring — the part with the most branches, and the likeliest place for a blip — runs on the far side of the gate and is not retried (it is an agent step, so it would not be retried in any case).
A failure a later pass genuinely fixed no longer sinks the run either. When a Map branch fails and the same Map, running the same step over the same item, succeeds on a later pass — a loop that re-enters that map — the earlier failure is superseded and the run can settle Completed. The match is on the item, not on its position in the list, so a later pass succeeding at a different section can never clear an earlier section’s failure. It is on the Map and its body step as well: a separate revise Map, with its own body step, is different work and supersedes nothing. That is the shape the RFP Response Pipeline has — re-authoring runs through a second Map rather than a second pass of the first — so a section that failed on the first authoring pass still fails the run there, however well the revise pass rewrites it. Any failure that nothing superseded still fails the run exactly as before.
A step can be red on a run that completed
A step that ran to completion but reported that it fell short is recorded as failed and shown red, even though it produced output. Two independent signals do this, both volunteered by the step itself: a status outside the success set (a model-declaredpartial, a budget cut-off, an error envelope), or a non-empty list of block failures — a tool the agent called died, which counts regardless of what the model then claimed about its own work.
This is deliberate. Before it, the step log was seeded success before a node ran and only ever downgraded if the node raised, so an agent that returned normally while reporting its own failure logged green on every surface a human approves from — and runs shipped documents with empty and stubbed sections that nothing anywhere marked. A green run that delivered a hollow section was the defect; a red step on a run that produced a file is the fix, not a regression.
Two consequences to hold onto:
- The run status is unchanged. A degraded step does not raise, does not alter routing, and does not fail the run — a run whose steps degraded still settles Completed. Instead, the completion message posted back to the conversation appends a note naming the step that stopped early and warning that parts of the document may be incomplete. (A run that genuinely fails still reports Failed, and the admin Run panel’s step log names the failing step and shows which steps never ran.) When the flow fanned out over a Map/Join, that failure no longer reports only the headline of the node that raised: the run’s error message — posted back into the conversation for a sub-agent run, shown on the Run panel for a canvas one — also names, per Join, how many branches were rejected or held back for review in the last pass, how many sections are recorded, and each named section with the underlying cause, plus a separate line for sections condemned by an earlier pass that no later pass re-produced cleanly. The wording carries the distinction that tells you what to do next: rejected means nothing of that section survived, held back for review means its text is recorded and was not cleared for use. The message is budgeted to fit the chat preview and the held-back sections are named first, so what gets truncated is the failing node’s own error — its full text is still on the run’s step log and error list in the Run panel.
- A degraded step’s output is still shown. It is not withheld the way a crashed node’s is, because that content is exactly what a reviewer needs in order to judge how bad the shortfall actually is.
Output length is deliberately not a degradation signal. On the runs this was built from, good sections spanned 2,844–18,339 characters and degraded ones 400–15,782 — the two classes overlap completely, so no threshold separates them and any value chosen would reject good work. A length floor is available as an opt-in per-flow setting on Join instead, and is a stub floor rather than a quality test.
Human-in-the-Loop (HITL)
Flows pause whenever they reach a Human review node (or a Knowledge Graph node configured to interrupt on clarification). A paused run waits for a reviewer to approve or reject (with a comment) before it resumes. Paused runs surface in the Agent Inbox at Admin > AI & Agents > Agent Inbox, which lists every run awaiting human input — newest pause first — with the paused node and a click-through to act on it. Runs you start from the canvas also surface their pause in the editor’s Run panel. Resuming a run is atomic: two reviewers cannot both resume the same pause. A gate can fail without the work it guards failing. When an agent step calls Human review as one of its allowed blocks and that block is the step’s only failure — the step returned normally, reported no problem of its own, and nothing but the gate died — the step is still recorded as failed and its branch still counts as failed at the Join, but its text is kept rather than thrown away, and is reported as held back for review rather than rejected. An unapproved section is not an untrustworthy one, so a flow with a revise lane gets another pass at it while the words already written stay recorded. If a content block failed in the same step, that is the whole story and the branch is treated as unusable exactly as before. A gate that cannot reach the store where it normally parks its payload still stops the run and shows the reviewer the full payload inline, rather than dying quietly while the flow authors on.Editing before approving
A reviewer is not limited to a yes/no on what the node proposed. Where the review payload is a document or a block of text, the card offers Edit: the reviewer rewrites the text — or a document’s title and each of its sections — and then approves. The edited version is what the rest of the flow receives, so a downstream Generate DOCX node renders the reviewer’s wording rather than the draft they were shown. An edited card is marked edited, and Revert restores the original. A very large payload is stored in the run record in elided form and fetched in full when the card opens; it is not editable until that full content has arrived, so nobody can overwrite a document they have not actually seen.What the conversation records
For a run launched from a conversation, each decision is written back as a message in that conversation: which gate it was, whether the reviewer approved or rejected it, and the note they left (a rejection with no note is recorded as having none). When the reviewer edited before approving, the message says so and carries what was approved — the edited version — so reading the thread back shows what actually went downstream, not the superseded draft. Answers to a mid-run question are recorded the same way. Each decision message is tied to the review it answers, so a partial resume or a re-settle cannot post it twice. The question and its answer are shown as one card. Both messages are still recorded — nothing is deleted — but the transcript renders them together: the answer appears underneath the question it answers, and the question’s “answer it in the run panel” call to action drops away once it has been answered, because it no longer applies. A long answer — a reviewer who rewrote a whole document before approving, say — is shown as a preview with a View full response control that opens the rest in the side panel. One case deliberately stays as two separate cards: an answer given after the user has already sent another chat message, so a verdict never appears above a later user turn.Several questions at once
A run can pause on more than one question at the same time — for example two Human review nodes that sit on parallel branches, or a review plus a Knowledge Graph clarification. When that happens:- Every pending question is listed together, and the reviewer can answer them in any order.
- A reviewer is never made to wait. An answer given while the run is still applying a previous one is held and submitted automatically as soon as the run frees up — it is not refused, and there is nothing to retry. Only the card being submitted right now is briefly unavailable; questions the run still lists as pending stay answerable throughout.
- An answered card clears, and the same question does not come back. A gate the flow re-raises carrying new content — a revise pass returning to the same Human review node — is a fresh question and is shown again: a card is suppressed on what it asked as well as on which gate asked it, so a re-settle of an unchanged question stays hidden while a genuinely re-asked one comes back and has to be answered again. The second answer is recorded in the conversation as its own decision rather than folded onto the first. Unanswered questions stay on screen, and the conversation’s amber review marker stays lit until the last one has been answered.
- Each question is answered on its own — there is no bulk sign-off. Approving or rejecting a card submits that card’s answer along with any edits made on it, and each decision carries its own note; a free-text question with an empty answer box has no defensible default and is never answered on the reviewer’s behalf.
The review payload shown to a reviewer (and any generated documents) is cleaned of raw
[evidence:…] citation markers, so reviewers see readable text. Inline citations are still preserved in the assistant’s chat answers.Templates
Experio seeds starter templates you can duplicate into your own draft:- RFP Response Pipeline — extracts requirements from an uploaded RFP, drafts sections, and produces a formatted response document.
- Grunley Scope Merge — merges uploaded scope-of-work documents into a single document.
- Knowledge Graph Report — answers a question from the knowledge graph as a cited narrative report.
- RFQ Generation — turns a scope of work into a supplier Request For Quotation, filling in an uploaded Excel RFQ template and returning the same workbook with its branding, formatting and formulas intact.
Delivering a template fix to flows already in use
A template is a starting point, not a live link. “Use template” copies the template’s definition into a new, independent flow; nothing records where that copy came from. This matters when a fix ships in a template, because the obvious assumptions are both wrong:- Re-seeding the templates does not change flows already created from them. It updates the template flow only.
- Re-publishing the derived flow does not pull the fix in either. Publishing snapshots that flow’s own draft, which still holds the definition copied when it was created. Versions are immutable, so nothing rewrites it in place.
Re-seeding the templates
Templates are seeded by a management command, run fromserver/:
Unchanged template <name> (v<N>). A changed one updates the template’s draft and publishes a new immutable version, logging Updated template <name> and published v<N>. A template that fails save-time validation aborts the command rather than seeding a broken definition.
Templates are matched by name. If a workspace somehow has two templates with the same name, the seeder updates the oldest (the one it originally created) and leaves the duplicate alone rather than guessing.
The seeder skips templates a person has edited
To protect a tenant’s own authoring, the seeder refuses to overwrite a template it did not write, and says so in the log:Skipping <name>: staff-published v<N> or Skipping <name>: edited draft. A template counts as staff-authored on either of two signals:
- any version of it was published by a person (versions the seeder creates have no publisher), or
- it has an unpublished draft that differs from its published snapshot — the seeder’s own draft is always identical to what it published, so any divergence is a human edit.
--force:
OVERWRITING staff-authored <name> (--force): their template edits are being discarded) so the choice is never invisible in a deploy log. Export the tenant’s definition from the flow’s Edit as JSON dialog before running it.
Getting the fix into the flow that runs
Re-seeding leaves you with an updated template and an unchanged flow. The direct way to close that gap is the delivery command, run fromserver/:
--write, and says so on the last line (DRY RUN — nothing was written. Re-run with --write to publish.). --template is repeatable and defaults to every built-in template. Derived flows are found by name prefix, which cannot reach a copy somebody renamed — name that one with --flow <uuid> instead (repeatable, and it needs exactly one --template). It changes which flows are considered, not what the command is willing to write to — the checks below still apply to a flow you named. A --flow id that matches nothing, or that names a template row, is called out as NOT DELIVERABLE rather than passing silently.
What it reads matters as much as what it writes. The definition comes from the shipping code, not from the library template row, so a template the seeder has frozen as staff-authored (the Warning above) still delivers. It skips a flow whose latest version a person published, or whose draft has diverged from that published snapshot, because delivery would revert that work; --include-human-authored overrides both, and reverting is then exactly what it does. It also skips a flow whose live version is pinned, and no flag overrides that one — a pin does not advance on publish, so the new version would never run. Unpin it first. A skip of either kind is logged as SKIPPED with the reason, so a deploy log says which flows were left alone.
Delivery validates the definition, names the nodes it would rewrite, publishes a new immutable version — runs in flight and paused runs keep the definition they started on — and advances the flow’s draft to match, so a later publish from the canvas cannot quietly undo it. Unlike pasting a definition into Edit as JSON, it keeps the flow’s own name and re-pins the internal id itself.
If you would rather not run a command, or the flow is one the command skips, the two manual routes still work:
- Create a fresh flow from the updated template (Use template), re-apply any customization, publish it, and switch the Sub-agent toggle and description over from the old flow; or
- Carry the definition across by hand — export the updated template’s definition from its Edit as JSON dialog, paste it into the existing flow’s, apply, and publish.
REST API
Agent Flows are also available over REST under/api/agent-flows/ for programmatic use:
A flow’s JSON carries
subagent_enabled (whether the chat agent can launch it) and its live-version pointers: live_version (the pinned version id, or null when the flow follows the latest published version) and live_version_number (the effective live version number).Reads are available to authenticated users; creating, editing, and publishing flows require staff permissions.