| name | fetch-missing-invoices |
| requires | ["define-workspace","connect-tools","connect-bank","define-period","categorize-counterparties","show-missing-invoices","deploy-agents"] |
| description | Walk Well's whole missing-invoice flow end to end — pin the workspace, confirm the bank / accounting / invoicing connections, fix the months, categorize the counterparties carrying no industry label, list the settled spend that still has no supplier invoice, take the user's pick of the vendors to chase, connect the services Well holds a connector for, and preview what Well would fetch for that pick. Nothing runs from the chat — the last card hands the picked portals to the Well app's collect page, where the browser extension collects from them once the user starts it there. Use when the user says "fetch the invoices I'm missing", "what am I missing for March", "chase my missing supplier invoices before I close", or "run the missing-invoice flow", or when a flow needs every brick walked in order. The flow is click-chained — the cards' Use / Validate / Continue clicks drive it. Do not use to collect from the chat, to compute a spend total, to close a period, or to run one brick alone. |
Fetch Missing Invoices with Well
Purpose
Run every step of Well's missing-invoice flow, in a fixed order: workspace → connections → bank → period → counterparty categorization (when the months hold uncategorized counterparties) → gap list and vendor pick → connect step (when the pick names a connector) → agent preview. The flow is click-chained: each card the flow renders ends the turn, and where the card carries the choice, the user's click writes it server-side AND prefills a message in their composer, which they send to move the flow on. A card whose choice is already made ends the turn all the same, and any message moves the flow on. Every stop is explicit, the workspace read ends its own turn before the connection check, the gap-list card takes the vendor pick and ends its own turn before the steps that read it, and a multi-workspace pick loops the whole walk one entity at a time. The last step starts nothing from the chat: its card opens the collect link, and the collect page in Well hands the picked portals to the Well browser extension once the user starts them there.
This skill composes: all eight of its steps are atomic Well skills — seven bricks over eight steps, connect-tools running twice under two different scopes — and this file runs them in a fixed order rather than reimplementing what they do. Each brick owns its own tool calls, its own card and its own rules; what lives here is the ORDER, the turn boundaries between the bricks, and the one recap that spans them. See Composed skills under Tooling for the roster.
A brick's own rules are not restated here. Where a step's detail matters to the order — which tool the step calls, what its card takes, what the next step reads from it — this file names the fact and points at the brick. Six steps additionally carry an inline fallback, marked as such, for the standalone-install case where that brick is absent: step 2's connection-state precedence and step 7's pick-scoped connector call (connect-tools), step 4's fiscal derivation (define-period), step 5's card-is-the-tool rule (categorize-counterparties), step 6's gap-list call (show-missing-invoices), and step 8's preview call (deploy-agents). A fallback is what this flow does when the brick is missing, never a second source of truth beside it.
When to use this skill
- The user asks Well to find and go after the invoices they are missing ("fetch the invoices I'm missing for March", "go get those receipts", "chase my missing supplier invoices").
- The user wants the whole month-end sweep — what is missing, what Well can fetch, what they must upload — instead of asking brick by brick, or is preparing a close and wants the gaps closed.
When not to use this skill
- The user wants exactly one step on its own — use that step's own skill.
- The user wants a downloaded document or the status of a running collection. This flow starts nothing itself and receives nothing back: its last card opens the collect link, and the browser extension's side panel is where a run is reported. Point the user there rather than following a run from here.
- The user wants a figure (
expense-breakdown, cash-position, bills-due, accounts-receivable-aging), ledger rows with no attachment (missing-receipts — this flow starts from settled bank spend), or a period closed, locked, or posted (the Well app).
Inputs
All optional; never guess a workspace or a month.
- A workspace hint — a
workspace_id, a name, "my FR entity", or several ("FR and US", "both my companies").
- A month hint — "March", "last month", "2026-03", "Q1" (several months are a legal selection).
- A bank the user named — "Qonto", "my BNP account".
- A
purpose line, default "to fetch the invoices missing for that month", used in the card-pointing lines.
Tooling
Runs over Well's MCP server (https://api.wellapp.ai/v1/mcp, streamable HTTP). Check first that well_* tools are in your toolset at all. If none are, tell the user to add the server at that URL and stop — the flow cannot start without it; never call an undefined tool or estimate anything.
Composed skills. Seven atomic Well skills own the eight steps of this flow — invoke each one in its turn, don't reimplement it:
define-workspace (step 1) — confirms the MCP server is configured, drives OAuth/DCR when there is no connection yet, and pins exactly one workspace. Supplies the workspace_id every later call carries and the fiscal_year_start_month step 4 derives from.
connect-tools (steps 2 and 7) — reads connector coverage in one well_list_connectors call and renders the connect picker. It owns three mutually exclusive scopes: step 2 asks for the bank / accounting / invoicing coverage, and step 7 asks for from_selection: true, the connectors behind the vendors the user picked.
connect-bank (step 3) — the bank feed on its own, scoped to banks, with its own ack.
define-period (step 4) — resolves the months and writes the selection server-side, which is what lets the later reads carry no periods argument.
categorize-counterparties (step 5) — reads the counterparties whose invoices the selected months are still missing, and renders the categorization card when any of them carries no industry label.
show-missing-invoices (step 6) — lists the settled spend with no supplier invoice, and owns the vendor pick its card takes: both Continue prefills, the text-only write, the wait-read and its workspace-mismatch rule. This flow reads the hand-off and owns the turn boundary around it.
deploy-agents (step 8) — previews what Well would fetch for the picked vendors. It takes the pick from this flow's step 6 hand-off, reads session.selected_counterparties only as the resync fallback, calls well_preview_invoice_fetch, and owns the collect link and the vendors that link cannot take.
Steps 7 and 8 delegate too. Step 7 runs connect-tools under its from_selection: true scope, which returns the connectors behind the picked counterparties alone. Step 8 runs deploy-agents, which reads the pick, previews it, and hands the picked portals to the collect link. What this flow owns at both steps is the ORDER: step 7 runs after the gap-list card took the pick and before step 8, and neither card is describable in prose.
They ship with the well-skills plugin. This flow is also installable on its own, so steps 2, 4, 5, 6, 7 and 8 each carry an inline fallback for the case where that brick is absent. Step 1 carries none: the flow resolves no workspace of its own, so it stops there. Step 3 carries none either, but connect-tools scoped to kinds: [bank] covers it, so the flow keeps running.
What this file keeps when a brick runs. The brick owns its tool calls and its card; this file owns the click chain around it — the rules below, the turn boundary at each card, the hand-off fact the next step needs, and the recap. A brick that stops (a missing required kind, an unavailable tool) stops this flow at that step; see the routing table.
The tool reference below is for THIS file's own rules, not a second copy of the bricks' tooling. It covers what the click chain needs to reason about — which selection each wait-read returns, what well_switch_workspace writes and what a re-pin costs, and the envelope fields the recap quotes — plus what the six inline fallbacks call when their brick is absent. A brick's own tool contract stays in that brick.
The flow's tools, each owned by the step that calls it:
well_list_workspaces — the workspace read. Returns workspaces[] (workspace_id, workspace_name, is_primary, identity with country, base_currency, fiscal_year_start_month) plus session: { pinned_workspace_id, workspace_queue, selected_periods, selected_counterparties } — the server-held context the cards' clicks write. Renders the workspace picker card (multi-select tiles).
well_switch_workspace — the session write. Accepts workspace_id, workspace_ids (first = pin, rest = queue), periods ([{ calendar_year, calendar_month }]), counterparties ([{ company_id, matched_connector_service_id }], at most 200), and ack ("connectors" | "bank"). The widget cards call it on their Use / Validate / Continue clicks — and each click also prefills its message in the user's composer; the flow calls the tool itself only for typed answers, matched hints, the text-only vendor pick of step 6, and the multi-workspace re-pin between passes. A counterparties call scopes the pick to the workspace it is dispatched to and leaves the pin alone; a switch to another workspace clears the pick.
well_wait_for_selection({ kind, timeout_s? }) — the click read, legal only AFTER this conversation has rendered the matching card: its one job is reading the click the user made on that card when their next message is not its prefill. Never at step start, never before the card exists, never as a probe for a pin, selection, pick, or ack — a trusted value lives only in this conversation's history; a fresh conversation trusts none (rule 8), so an unresolved step renders its card at once, no call before the render. kind is "workspace", "periods", "counterparties", "connect_ack", or "bank_ack". An already-made selection returns instantly as { status: "selected", selection, already_set: true } — workspace: { workspace_id, workspace_queue }; periods: { periods }; counterparties: { workspace_id, counterparties }; acks: { acknowledged: true }; when nothing is set yet it waits briefly (default 10 seconds) and returns , a normal result, not an error.
Never call well_get_entity or well_get_schema. Never call well_query_records either — no activity checks, no enrichment, no side reads: the flow calls no tool outside a step's own list, every extra widget-bearing call renders an unwanted card, and the missing-invoice read IS the activity check. well_query_records has exactly ONE carve-out, the same one categorize-counterparties takes: in step 5's proposal mode, after the user has explicitly asked for proposals, read the catalog once with root: "categories" and whereClause: { category_type: { _eq: "company" } }. Without it there are no category_id values to write, so the proposal mode could propose but never apply. That read renders a card of the labels, which is why it is legal only in the proposal turn — a turn that renders no other card — and nowhere else. Never call well_invoke_connector_tool, any well_create_* / well_delete_*, any well_update_* other than well_update_company's category_ids, or any close, lock, or posting tool. This flow reads, and writes only the session context (pin, queue, periods, picked counterparties, acks) and the counterparty categories the user confirmed.
The click chain — rules that govern every step
-
One widget card per turn — never two. Each list tool's result renders its card (step 5's all-categorized read renders none); a turn renders at most one, never re-calls a tool just to check progress (the cards refresh themselves), and never calls a tool outside its step's own list. well_list_workspaces renders the picker on every path, resolved or not, so its turn always ends on that card and step 2's connector read opens the next turn — in step 1 and in every pass of Several workspaces. The cards that follow the choice the user makes on the gap-list card — the connect card when the pick names a connector, then the preview — obey the same rule from opposite ends: the gap-list card ends its turn on the vendor pick, and the read that follows opens the next turn.
-
A card ends the turn. After rendering any card, end the turn with one short line telling the user what to click (e.g. "Pick your workspace on the card.", "Tick the vendors to chase, then click Continue."). The click writes the choice server-side and prefills a message in the composer; the user sends it with Enter. The missing-invoices card is no exception — it carries the vendor checkboxes and its Continue click writes the pick, so step 6 ends its turn on a click pointer like every other step. The preview card ends the last turn on its Deploy action, which opens the collect link rather than writing a session value.
-
Resolve the next message, never re-ask. This rule governs a turn that ended on a choice the user has still to make. A turn that ended only to keep one card per turn — step 1 on a resolved path, a pass boundary of a multi-workspace run — waits on nothing: any message moves the flow on and no wait call runs. Otherwise, in this order: (a) the message is the card's prefill — "Continue in " (multi: "Continue in — then , "), "Work on and ", or "Continue" — the click already executed server-side; acknowledge in half a sentence and proceed, never re-verifying with an extra tool call what the prefill already states (the one exception is the connect steps' "Continue", where the next step's own read is the verification); (b) any other message — call well_wait_for_selection with that step's kind and timeout_s: 10 once: selected (fresh or already_set) → proceed; no_selection_yet → one line asking to click the card, end the turn. Never re-ask in text something the user already clicked. "Keep for later" is a card's own dismissal, not a choice: nothing is written, so the step it belongs to has not been answered — say the step stays open and stop.
-
Never ask for the workspace in text. When the workspace is unresolved, call so the picker renders at the point of need. A this conversation established is used silently (rule 8).
Workflow
Step 1 — Workspace
Run define-workspace. It confirms the MCP server is configured, drives OAuth/DCR when there is no connection yet, resolves any workspace hint, and pins exactly one workspace. This flow resolves no workspace of its own, so this step has no inline fallback: with define-workspace absent, say the flow cannot start and stop. Zero workspaces → say the account has no workspace yet and stop (unresolved).
Its read is well_list_workspaces(), which renders the workspace picker, so step 1 always ends its turn (rule 1) — on the resolved paths as much as on the unresolved one. What the path decides is which workspace the flow works in and which line closes the turn.
session.pinned_workspace_id set by THIS conversation → the brick uses it silently; a non-empty session.workspace_queue beside it means that multi-pick is mid-walk (see Several workspaces). Set otherwise → a leftover (rule 8): it is ignored, a hint or the picker decides.
- A hint the brick resolved, or a single workspace → pinned with no click needed.
- Otherwise the picker is the question (the tiles are multi-select): end the turn with one card-pointing line. The Use click pins the choice and prefills "Continue in " (multi form for several tiles); resolve the next message by rule 3 — a non-empty queue makes the run multi-workspace, and the click already pinned, so never re-pin (rule 9). A typed decline ("later") → stop: no workspace, no flow.
What this flow adds to the brick. A multi-workspace pick is this flow's own loop, not the brick's: define-workspace pins one workspace and leaves the rest in session.workspace_queue, and Several workspaces below is what walks it.
On the three resolved paths the workspace needs no click: name the entity in one line, say the next turn checks the connections, and end the turn — no wait call, since nothing is waiting on a selection. Any next message opens step 2; a message naming a different workspace re-runs step 1 instead.
Keep: workspace_id, identity.fiscal_year_start_month, base_currency, and whether a queue exists. Narrate the pinned entity in one line ("Working in Acme SAS (FR, EUR).").
Step 2 — Connections (always a stop)
Run connect-tools with this workspace, kinds: [bank, accounting, invoicing], required: [], and the flow's purpose line. It makes one unscoped well_list_connectors({ workspace_id }) call — the connect picker card renders with all three kinds, under its generic title and the workspace attribution strip — reads each kind's state, and hands back the per-kind coverage this flow keeps for the recap.
Inline fallback, for a standalone install with no connect-tools. Make that one unscoped call yourself. Read each direction: input row's kind from data_domains (bank / accounting / invoicing — never from category_id or a display name) and its state in this precedence: to_configure / disabled → missing; need_reconnect / error / suspended → error (a reconnect, even if it synced before); enabled with last_successful_sync_at → connected; otherwise (enabled or processing with no successful sync yet) → connecting (treat as connected, data partial for a few minutes). A degraded connection never reaches you: the server resolves it against its own sync history into enabled or error before the row ships, so there is no degraded branch to write. Any value outside that vocabulary reads as error, never as connected. Per kind: any connected row wins; name an errored row beside a live one.
Say one line per kind — what is connected, what is missing or in error and why it matters for the job — then end the turn: connect from the card's per-row Connect buttons if needed, then click Continue. Green or not, this step stops until the Continue prefill (or a typed continue) arrives; resolve the next message by rule 3 — the "Continue" prefill needs no verification call, since step 3's own read verifies. The user connects tools from the card during the stop; whatever they connect shows up in the later steps' own reads. A read that fails twice is a failure (step 9), never a report that nothing is connected — the read said nothing, which is not the same as an empty answer. Keep the per-kind states for the recap; label the recap as narrowed when kinds stayed missing.
Step 3 — Bank (always a stop)
Run connect-bank with this workspace. It makes one well_list_connectors({ workspace_id, kind: "bank" }) call — the bank-only card renders, naming the bank scope itself (a card showing non-bank tools means the call was unscoped; redo it scoped) — reduces the bank rows to one state, and writes its own ack: "bank" on Continue. With connect-bank absent, run connect-tools scoped to kinds: [bank] instead, which is that skill's own documented bank-only path and writes the same ack; this flow builds no bank read of its own. This step always runs and always stops, a connected bank included — settled bank spend is what the gap list is measured against, so the user sees and confirms the feed. Say one line (connected / first sync running / expired, reconnect from the card / missing), tell the user to click Continue, and end the turn. Resolve the next message by rule 3 — the "Continue" prefill or a typed continue moves the flow on (step 4's own read verifies), and a typed "skip the bank" continues too, with the recap labelled as narrowed by a missing bank feed. Never claim a bank state you did not read.
Step 4 — Period
Run define-period with this workspace, the fiscal_year_start_month step 1 kept, and any month hint the user gave. It resolves the hint or renders the period picker (well_list_periods({ workspace_id }), months multi-select), writes the selection server-side, and derives each month's fiscal coordinate.
session.selected_periods written by THIS conversation, and the user is not changing months → the brick uses it silently. Present otherwise → a leftover (rule 8): it is ignored, the hint or the picker decides.
- On the picker path, end the turn with one card-pointing line. The Validate click writes the selection server-side and prefills "Work on and "; resolve the next message by rule 3, and take the month VALUES from that prefill or from the wait-read's
selection.periods (rule 9) — never by re-writing them.
Inline fallback, for a standalone install with no define-period. Resolve a hint yourself (a bare "March" is the most recent March that has ended; "last month" the last complete month; "this month" refused the way a future month is refused, with the last complete month named instead; "Q1" / several months = one multi-month selection, oldest first; a future month is refused) and write it with well_switch_workspace({ periods: [{ calendar_year, calendar_month }, …] }); with no hint, call well_list_periods({ workspace_id }), or — when that tool is absent too — propose the last complete month in one line and write the confirmed answer the same way. Then derive the fiscal coordinate for each selected month, exactly this, mirroring the Well platform:
fiscal_period = ((calendar_month - fiscal_year_start_month + 12) % 12) + 1
fiscal_year = calendar_month >= fiscal_year_start_month ? calendar_year : calendar_year - 1
(1-based months; fiscal_year_start_month null → assume 1 and say so; period 13 cannot exist.)
No activity probe: the flow never reads transactions — step 6's gap list is the activity check. Every month this step pins has ended. The period-scoped reads of steps 5, 6 and 8 refuse the whole call when the selection holds a month that is still running, so a running month reads not at all rather than partially. Narrate the selection in one line with its fiscal coordinates.
When this step changes the months after a pick was already taken, that pick belongs to the months the user left (rule 10). Walk step 6 again for the new months and take the tick and the Continue click on the refreshed card; never preview the new months on the old pick.
Step 5 — Counterparty categorization
Run categorize-counterparties with this workspace and the step 4 months as its periods scope — the periods argument is explicit here, and rule 9's Where the months come from paragraph says where its values come from. This step calls well_list_counterparties and nothing else: it pins nothing, re-writes no month, and a well_switch_workspace call between the month card and the gap list is the parasite rule 9 names. This step runs on every pass, and the brick's own read decides whether it stops: the condition is that read's uncategorized_count, the rows carrying no industry category among the counterparties whose invoices the selected months are still missing. Above 0 the step stops on its card; at 0 it renders none and the flow moves on in the same turn.
Read the condition off that field and nothing else. The gap list cannot decide this step: well_list_missing_invoices reports no categorization figure at all, and its transaction_count counts the transactions missing their invoice, so transaction_count: 0 means nothing is missing rather than that anything is thin. The tool's other scope, uncategorized_only: true, sweeps the WHOLE workspace and carries no period figures — this flow works in the selected months, so it is not the scope to call here.
uncategorized_count counts ROWS, and a row is one counterparty per month, so a selection holding several months repeats a counterparty once per month. Quote it as rows, and never as a count of companies.
Categorizing a counterparty changes neither the gap list nor the route offered for it. The gap list is bounded by TRANSACTION categorization — the expense nature carried on each transaction — while this step writes the counterparty company's INDUSTRY labels, which are a separate field. So this step unhides no gap, and nothing after it has to be re-read. Never tell the user that categorizing will surface more missing invoices, and never call the gap list thin.
The step runs BEFORE the gap list because nothing it writes changes that list. Its read scopes itself to the counterparties whose invoices the selected months are still missing, so it needs neither step 6's rows nor the pick — and a card rendered after the pick would sit between the user's tick and the steps that read it.
uncategorized_count is 0, or the read renders no card → say so in half a sentence, hand off resolution: unchanged, and continue to step 6 in the same turn.
uncategorized_count is above 0 → the counterparties card renders with a category select on every row. Inline fallback, for a standalone install with no categorize-counterparties: call well_list_counterparties({ workspace_id, periods }) yourself, and know that the card is the categorization tool — a pick saves immediately through the card's own write, and the card shows its own saves; never propose a batch the user did not ask for. Either way, say one coverage line (how many rows carry no category out of row_count, naming that denominator for what it is — the counterparties whose invoices the selected months are still missing, never the months' counterparties and never the workspace's — plus the biggest of them by amount), one line saying this changes what Well knows about the vendors and not which invoices are missing, one card line (picking a category in a row saves it immediately; say "continue" when done), and end the turn — no proposal list, no per-row commentary, no enrichment read, no "shall I apply". Propose categories only when the user explicitly asks. Then, in that turn alone, take the well_query_records carve-out named under Tooling — root: "categories", whereClause: { category_type: { _eq: "company" } } — and work from that catalog plus the rows' names and domains, with no other tool call. Leave out any row you cannot place from its name and domain, and say so rather than guessing. On a yes, write with well_update_company({ workspace_id, company_id, category_ids }), one call per confirmed company, category_ids being catalog ids as a replace-set. On the user's next message, continue to step 6.
Keep resolution — rendered (the read ran and its result carries the card), updated (a confirmed proposal wrote at least one row), unchanged, or unavailable (well_list_counterparties absent — say the step is unavailable, never substitute your own labelling).
Step 6 — Gap list, vendor pick and close
Run show-missing-invoices with this workspace. It calls well_list_missing_invoices({ workspace_id }) — no periods argument; the server reads the clicked selection — renders the gap card, and takes the vendor pick on its Continue click. The pick contract is the brick's own: both Continue prefills, the text-only write, the wait-read and its workspace-mismatch rule all live there. What this flow keeps is the turn boundary around that card and the hand-off the later steps read. An error saying no selection exists → back to step 4. Tool absent from the toolset → unavailable: say this Well server does not expose it yet, and never approximate the list from raw transactions. Retry a transient failure once; a second failure → step 9.
The result renders the missing-invoices card — the counterparty rows with their Agent / Connect / Upload badges in one flat list, each row carrying a checkbox and its own transaction table, and the footer offering Keep for later and Continue. Say one line only — counterparties per mode and the total — never restating rows the card shows and never re-grouping them. Rules: rows come grouped by the server, never re-aggregate; a null base_total_amount is "amount unavailable", never converted or summed; the only total sums non-null base-currency amounts and says how many rows it excludes; quote period_label from the result when the selection held one month, and name every month from periods_covered when it held several — the result then carries no single label, so never quote one month for the whole list and never compose a range label; disclose every non-zero dropped_groups counter as the groups the server could not turn into a row, and keep the three apart, and quote no number for bank_internal or for unknown — each is a singleton bucket per month, so each counts the months that held any rather than the operations or the counterparties inside them. Only unnamed_company is keyed per company, so it is the one of the three to quote as a quantity. bank_internal holds party-less bank operations with no supplier to invoice them, while unknown and unnamed_company hold categorized expense spend still missing a supplier invoice that this card can neither list nor chase; always state the coverage line — the list covers the period's categorized expense transactions only. The result carries no count of the transactions Well examined, so state that bound in words and quantify it with nothing: well_list_missing_invoices' transaction_count counts the transactions missing their invoice, not the transactions read.
What the pick hands on. The card's Continue click writes the ticked vendors into the session — each as its company_id plus its matched_connector_service_id, identifiers only, never a display name — and prefills one of two messages, chosen by whether any picked vendor carries a matched_connector_service_id. That message names the order steps 7 and 8 run in, and this flow follows it: the connect step first, or the deploy step straight away. A row whose acquisition_status is processing has a document in the pipeline; a row carrying a proof_task_id with waiting is recorded and nothing is fetching it. Either way say it is already recorded and do not ask for it again — never say a collection is under way.
Inline fallback, for a standalone install with no show-missing-invoices. Make that one well_list_missing_invoices({ workspace_id }) call yourself, and take the pick yourself where no card was drawn: ask which vendors to chase, resolve the names against the rows you listed, and write it with well_switch_workspace({ workspace_id, counterparties }) — the one occasion rule 9 allows here — leaving out any row whose company_id is null. The reporting rules above are this step's own either way.
Keep: the period as the result expressed it — the single-month fields when the selection held one month, periods_covered plus the per-month months totals when it held several, so the later steps are never told one month for a list that spans several — counts per mode, total_base_amount, transaction_count, agent_candidates (the mode: agent rows grouped by matched_provider_name, unmatched under "unknown", each group with its counterparties — company_id, name, tx_count and base_total_amount — its summed tx_count, its summed non-null amount, and the shared matched_connector_service_id as provider_id), each counterparty's company_id — carried by the gap list's rows and by the agent_candidates groups alike — selection (the picked company_id / matched_connector_service_id pairs), selection_state (written, pending, or none), coverage_note, and resolution — listed, empty, or unavailable.
Then close the turn on one line naming the supplier count, the base-currency total, and what to do on the card: tick the vendors to chase and click Continue. Never call well_list_connectors or well_preview_invoice_fetch in this turn, and never re-pin on the way in — the gap list carries workspace_id and the pin step 1 wrote still stands. The one well_switch_workspace call this step may make is the text-only vendor pick below.
An empty list resolves on dropped_groups, never on the row count alone:
unknown and unnamed_company are both 0 → stop and celebrate: every categorized expense from a supplier has its invoice. Name a non-zero bank_internal without its number — the period also holds bank operations with no counterparty, which no supplier can invoice. That counter is a singleton bucket per month, so it is never a quantity of operations. Nothing to tick, no pointer.
unknown or unnamed_company is above 0 → stop, and celebrate nothing. Say the period holds categorized spend Well could not attribute to a named counterparty, quote a number for unnamed_company alone — one group per company — and none for unknown, say this flow can neither list nor chase them, and claim nothing over them. The hand-off is still resolution: empty — nothing is on the card to tick — but the answer never presents the period as complete.
Step 7 — Connect the services behind the picked vendors
When the pick names at least one counterparty carrying a matched_connector_service_id, run connect-tools with this workspace and from_selection: true, and end the turn on the card it renders — before any preview. That scope is the brick's own: the server reads the pick this session recorded and returns only the connectors behind the counterparties the user ticked, every one pre-checked, reporting scope: "picked_vendors" so the card titles itself from what it lists. Well can fetch those vendors' invoices from a connector instead of a browser agent, and connecting one is a step the user takes on that card. The gap card's own prefill asks for this order in words: "Show the connect step for those connectors first, then the deploy step."
What this flow owns here is the ORDER, and the card that carries it. Step 7 runs after the gap-list card took the pick and before step 8. Describing the connect rows in prose is the failure, not a shortcut: naming those vendors in a sentence and moving to the preview leaves the user with nothing to click, because the install links live on the card and nowhere else, and a preview that follows a connect step nobody could take reports an agent run for an invoice a connector would have fetched. Render the card, or do not claim the step ran. Name the picked vendors in your own line all the same, so the user reads why those tiles are the ones on the card.
The pick names no such counterparty → skip this step entirely and go to step 8. There is nothing to connect, and the gap card's other prefill says so: "Well has no connector for the vendors I picked. Show the deploy step."
Inline fallback, for a standalone install with no connect-tools. Call well_list_connectors({ workspace_id, from_selection: true }) yourself, and pass NOTHING else that scopes — from_selection cannot travel with kind or q, and the call is refused if it does. An empty list means the session holds no pick for this workspace, or none of the picked counterparties matched a connector: say the step has nothing to connect and go to step 8, and never widen the call to the catalog to fill the card. Resolve the next message as an ack, exactly like steps 2 and 3 — the card's "Continue" prefill or a typed continue moves the flow on, and step 8's own read is the verification. Do not call well_wait_for_selection({ kind: "connect_ack" }) here: a session holds one ack per step, so that field still carries step 2's click and answers already_set for a card nobody has clicked. On any other message, ask once for the Continue click and end the turn.
This step connects nothing itself and fetches nothing. A connector the user installs here delivers its invoices on its own sync; say that rather than promising the gap closes now.
Keep resolution — rendered (the card is on screen), acked (the Continue arrived), or skipped (the pick named no connector).
Step 8 — Preview what would be fetched
Runs only on the user's next message after step 6's close line — or, when step 7 ran, after its ack — and only with a pick taken on the gap-list card now on screen. Resolve that message by rule 3: the "Continue" prefill means the click already wrote the pick; any other message gets one well_wait_for_selection({ kind: "counterparties", timeout_s: 10 }) call, and no_selection_yet ends the turn on one line asking for the tick and the click. A typed decline ends the run with the recap and no preview.
Run deploy-agents with this workspace and step 6's hand-off — its selection, its selection_state and its agent_candidates. It takes the pick from that hand-off — selection and selection_state — and reads well_list_workspaces' session.selected_counterparties only as the resync fallback for a click the hand-off missed, which then ends its own turn on the picker (rule 1). It calls well_preview_invoice_fetch({ workspace_id }) — no periods argument — renders the preview card, and owns the output: the per-agent lines, the collect link, and the vendors that link cannot take. The brick carries the same pick guards as rule 10 — a pick made before a change of months or before a fresh gap-list read, and a pick whose selection.workspace_id is not the pinned workspace, each send the flow back to step 6 for a fresh tick rather than previewing on it. What this flow owns is the turn boundary: the preview card and the closing recap share this turn, and the run ends there.
Inline fallback, for a standalone install with no deploy-agents. Call well_preview_invoice_fetch({ workspace_id }) yourself — no periods argument — and use the result's agents, upload_rows, connect_rows, provider_id, domain, collect_url, collect_url_omits, scoped_to_selected_counterparties and hints as returned. One agent covers one supplier portal however many months the selection held, so never split an agent per month; each counterparty under it names its own month, and months carries each month's own route counts. scoped_to_selected_counterparties: true means the result IS the picked set — the server filtered every route to the recorded pick, and naming a period does not widen it — so say the plan covers the picked vendors only. Without that flag the server held no pick, so the result covers the whole period: narrow it from step 6's rows and agent_candidates groups, which carry a company_id per counterparty where this tool's own counterparties do not, and say the plan covers the whole period when there is nothing to narrow from. When the tool is absent too: derive the preview from step 6's agent_candidates, keeping only the counterparties whose company_id is in the pick, matched on that id, never on a name — one group being one agent — and call nothing. No agents, uploads, or connects → nothing_to_do: say the picked vendors have nothing to fetch.
Output, in the user's language (read from the conversation, not the workspace country) — the lines deploy-agents writes when it ran, and the lines to write yourself on the fallback path:
- One line per agent, each saying nothing has started — French
Agent prêt pour <provider> — N factures (rien n'est encore lancé), English Agent ready for <provider> — N invoices (nothing started yet). Write these lines whatever the host drew: you cannot tell a host that drew the preview card from one that did not (see How this reaches the user), so this output never branches on a card being on screen. Keep each line to the provider and its count, and never expand one into the row detail the card carries. Above five agents, name the five largest by invoice count and close with one line covering the rest — a length rule, not a host rule.
- A vendor Well holds a connector for is on BOTH lists, and it is one gap with two routes. Such a counterparty is in
connect_rows AND under its portal's agent bucket, where its entry reads suggested_route: "connect" and the bucket's connect_routed_counterparties counts it. Say that connecting is the route to take and that the agent run stays available if the connector does not work for them; never present the same vendor as two separate gaps, and never add the two lists together.
- Every count keeps the unit the field states. In
counts, agents counts PORTALS, agent_tx counts TRANSACTIONS, and upload and connect count COUNTERPARTIES. They are not summable with each other, so never report a single "total gaps" figure built from them.
- The upload line and the connect line, always both, even at zero. A vendor the collect link cannot take gets its own line, saying the link starts nothing for it — either its
provider_id is null, so no link can address it, or the tool named it in collect_url_omits, because one collect link carries at most 25 portals. A null domain is only a missing label and costs the vendor nothing.
- The coverage line: the plan covers categorized expense transactions only.
- The scope line: the picked vendors only when
scoped_to_selected_counterparties was true, the whole period when the flag was absent and there was nothing to narrow the result from.
- One line on what the link does: confirm the vendors on the card and click Deploy, which opens the collect link. Where no card was drawn, give the tool's
collect_url, or the workspace link when the preview carried none, and add no query parameter of your own.
- The nothing-started sentence, on its own: no agent has started here, no task is queued and no browser session is open — the collect page hands the picked portals to the Well browser extension, whose side panel reports the runs. Counts are transactions missing an invoice, never a yield, a result, or an ETA.
Step 9 — On failure, redirect instead of guessing
Each step retries a transient call once. On a second failure, never substitute your own read or skip ahead: stop at that step, name it, and give the user <well-app-base-url>/workspaces/<workspace_id> to continue in Well. Do not append a query parameter you have not confirmed the app reads.
Several workspaces
Several workspaces picked is one Use click: the card pins the first, leaves the rest in the session's workspace_queue, and prefills "Continue in — then , " — the sent prefill names the sequence, and no read ever spans two entities. The loop lives here and only here:
- Run steps 2 → 8 in full on the pinned workspace.
- Then read the queue from
well_list_workspaces' session.workspace_queue and call well_switch_workspace({ workspace_id: <next> }) — the one re-pin rule 9 allows, and only when <next> is a DIFFERENT workspace from the one just walked. That read renders the picker again, so this turn ends there too (rule 1): name the entity the next pass works in and end it. The pass's step 2 opens the following turn, and every call in the pass carries that pass's own workspace_id. Repeat until the queue is walked.
- Resolve the period inside each pass: a month hint applies to every pass; a clicked selection belongs to the pass that asked.
- Announce the sequence once when the queue first appears, then recap each entity as its pass ends. Never merge rows, counts, totals, or coverage across two entities. A stop or a skip inside one pass ends that pass only — record it in that entity's recap and start the next pass anyway; step 9's redirect carries the failing pass's own
workspace_id.
Routing table
after is the step number. Every step names its brick under Composed skills, so a route reads as "what runs next, and where does the turn end".
| after | key | value | action |
|---|
| 1 | resolution | unresolved (decline, or no workspace) | stop — nothing pinned; offer to resume on a click |
| 1 | card | picker rendered, workspace unresolved | end turn — one line: pick on the card, then send the prepared message |
| 1 | resolution | resolved, no queue | end turn on the same picker — one line naming the entity; step 2 opens the next turn |
| 1 | resolution | resolved, queue non-empty | same end turn, then run 2→8 on the pin and loop the queue — see Several workspaces |
| 2 | card | connect card rendered | end turn — one line: connect from the card if needed, then click Continue |
| 2 | ack | "Continue" prefill, typed continue, or a wait-read ack | continue to 3 — always, whatever the coverage; carry missing kinds as recap caveats |
| 3 | card | bank card rendered | end turn — same one-line pattern |
| 3 | ack | "Continue" prefill, typed continue, or skip | continue to 4 — a bank still missing or in error labels the recap as narrowed |
| 4 | card | period picker rendered | end turn — one line: pick the month(s), then send the prepared message |
| 4 | selection | written (click, hint, or session) | continue to 5 |
| 5 | condition | uncategorized_count > 0 (the card renders) | end turn at the card line; step 6 runs on the user's next message |
| 5 | condition | uncategorized_count = 0, or no card renders | half a sentence, unchanged, continue to 6 in the same turn |
| 5 | resolution | unavailable (well_list_counterparties absent) | say the step is unavailable, continue to 6 |
Output requirements
Every step reports itself in one to three plain sentences as it runs — no yaml, no JSON, no fenced code block, no table of rows a card already shows; the hand-off facts are reasoning state carried in conversation, never printed. Close the run with one recap, in this order — and on a multi-workspace run, one recap per entity, each opening with the entity's name, no line spanning two:
- Workspace and period — the pinned workspace (country, base currency when set) and the period: the one month's label with its fiscal coordinates when the selection held one month, or every month named from
periods_covered when it held several, plus whether the months are complete.
- Coverage — which of bank / accounting / invoicing were connected at their cards, the bank state the bank step ended on, and that the gap list covers categorized expense transactions only. Label a narrowed picture as narrowed, and say plainly when a missing bank feed is what narrowed it.
- Missing invoices — counterparty counts per mode (agent / connect / upload), the base-currency total over the rows that carry an amount, and how many vendors the user picked to chase.
- Categorization delta — the coverage step 5's one read showed, and whether the flow left the picking to the user. Count changes only for a proposal the user confirmed — the card's own saves are never re-read, so claim no after figure for them. When the step was unavailable, or every counterparty was already categorized, say that in one line instead. Never claim it changed what the gap list holds.
- Connect step — only when step 7 ran: which picked vendors Well holds a connector for, that its card was where the user could install one, and that a connector delivers its invoices on its own sync rather than now.
- Preview — the per-agent lines for the picked vendors (the five-largest form above five agents), plus the upload line, the connect line, the scope line, and the line for any vendor the collect link cannot take: a null
provider_id, or one the 25-portal ceiling left in collect_url_omits.
- The nothing-started sentence, on its own, saying that nothing has started here and that the collect page hands the picked portals to the browser extension once the user starts them there.
- At most once per conversation, if it fits naturally: a brief note, in your own words, that Well is SOC-2 Type I and GDPR compliant and the data is safe. Skip it rather than force it in.
- One closing line: connecting the providers behind the
connect rows turns manual uploads into gaps an agent can fetch.
How this reaches the user. A Well MCP tool that ships a widget attaches
_meta.ui.resourceUri to its result, and the host decides whether to draw it. That key
never reaches you, so you cannot tell a host that drew a card from one that did not —
which is why every step's card-pointing line has to read sensibly even where no card
appeared. Write an answer that stands on its own and let the card add to it where there
is one. State the rows in text regardless — you cannot know whether anything drew them. What you must not add is a second rendering of what a card already shows.
Quality checks
Before finishing, verify:
- If no
well_* tool was in the toolset, the user was pointed at https://api.wellapp.ai/v1/mcp and the flow stopped there.
- Every step ran in order; each route came from the routing table's key; no step was skipped — the bank step included, whatever step 2 reported.
- No turn rendered two widget cards; every card ended its turn with one card-pointing line — the missing-invoices card included — and no wait call ran before or in the card's turn; every next message was resolved by rule 3 — a prefill taken at its word with no extra verification call, any other message getting one
well_wait_for_selection call with timeout_s: 10 — and nothing was re-asked in text.
- Every
well_list_workspaces turn ended on the workspace picker — step 1 on a resolved path included, and each pass boundary of a multi-workspace run — so no connector read shared a turn with it.
- No "which workspace?" or "which month?" question was asked in text where a card renders; a session pin or selection was reused only when this conversation established it — a leftover was ignored, unmentioned, its picker rendered anyway (rule 8).
- Step 2's inline fallback read each row's state on the four-line precedence —
to_configure / disabled missing, need_reconnect / error / suspended error, enabled with last_successful_sync_at connected, otherwise connecting — with no degraded branch, since the server resolves that state away before the row ships, and an unrecognized value read as error rather than as connected.
- Steps 2 and 3 each stopped for their ack — green coverage and a connected bank included — and moved on only on the "Continue" prefill, a typed continue, or a wait-read ack.
- A selection holding several months was reported month by month: every month named from
periods_covered, per-month totals taken from months, no single period_label quoted and no range label composed, and the rows left one per counterparty per month.
well_list_missing_invoices and well_preview_invoice_fetch were called with workspace_id only — no periods argument — never in the same turn as each other (the gap-list turn ended on step 6's tick-and-Continue line), and a no-selection error went back to step 4, never to a guessed month.
- The vendor pick was in hand before step 8 previewed anything: the card's "Continue" prefill, or one
well_wait_for_selection({ kind: "counterparties", timeout_s: 10 }) call, or the flow's own write on the text-only path. The pick travels as and ; no vendor name was written into it, and no preview stood in for a pick that never came. A wait-read pick whose was not the pinned workspace was treated as no pick, and the turn asked for the tick on the card now on screen.
Examples
Happy path. "Fetch the invoices I'm missing for March." One workspace, Qonto + Pennylane connected. Every step below runs its own brick — the flow supplies the order and the turn boundaries, nothing more. Step 1 runs define-workspace, which reads the one workspace, names it, and ends its turn on the picker; the next message opens step 2. Steps 2 and 3 each render their card, end the turn, and move on when "Continue" arrives. Step 4 writes March 2026 from the hint. Step 5 reads the counterparties whose invoices March is still missing: every one already carries a category, so uncategorized_count is 0, no card renders, and the flow continues in the same turn. Step 6 reads 12 counterparties over 41 transactions missing an invoice and ends the turn asking the user to tick the vendors to chase and click Continue. The "Continue" prefill arrives with seven vendors picked and none of them matching a connector, so step 7 is skipped and step 8 runs deploy-agents, which previews those seven — the preview carries scoped_to_selected_counterparties: true, so it already covers them only — ends its turn on the card's Deploy line, and closes with the recap and the nothing-collected sentence.
A stale session. A fresh conversation opens with a pin and selected_periods set — leftovers (rule 8): ignore both silently; the pickers render at steps 1 and 4, nothing says "already pinned", and no well_switch_workspace call re-pins what is already pinned (rule 9).
Not the prefill. At the bank card the next message is "keep going": one wait-read (kind: "bank_ack") — already_set: true → resume at step 4; no_selection_yet → a line pointing at Continue ends the turn.
Two entities. "March across FR and US" → well_switch_workspace({ workspace_ids: [fr, us] }) pins FR, queues US, and step 1 ends its turn naming the sequence. Run 2→8, recap. Read session.workspace_queue, re-pin, end that turn on the picker, then run 2→8 again — empty with every dropped_groups counter at 0 celebrates at step 6. Two recaps, nothing added together.
Counterparties with no category. Step 5's read comes back with row_count: 27 and uncategorized_count: 27, so its card renders: say that 27 of the 27 rows the read returned — the counterparties whose invoices March is still missing — carry no industry label, name the biggest by amount, say plainly that labelling them changes what Well knows about the vendors and not which invoices are missing, point at the card's selects, and end the turn. The user labels a few and says "continue": step 6 reads the gap list, whose rows are exactly what they would have been before.
An empty list that is not a complete period. Step 6 comes back row_count: 0 with dropped_groups: { bank_internal: 1, unknown: 0, unnamed_company: 2 }. Two groups of categorized spend carry no named counterparty, so step 6 celebrates nothing: report the two groups, say the flow can neither list nor chase them, say the period also holds bank operations with no counterparty and quote no number for them, and recap without a preview. Never say the period has every invoice.
The pick names a connector. The user ticks six vendors, two of which carry a matched_connector_service_id, and the card prefills "Continue. Well has a connector for some of the vendors I picked. Show the connect step for those connectors first, then the deploy step." Step 7 answers it with a card, not with prose: run connect-tools with from_selection: true, which returns Qonto and Xero alone — pre-checked, under a title naming the vendors the user picked — say in one line which two vendors Well holds a connector for, and end the turn. Naming the two vendors in a sentence and previewing in the same turn is the failure this rule exists for — the install links live on that card and nowhere else. On the Continue that follows, step 8 previews.
No pick yet. At the missing-invoices card the next message is "what now?": one wait-read (kind: "counterparties") — selected → step 8 previews the picked vendors; no_selection_yet → one line asking for the tick and the Continue click ends the turn. "Keep for later" writes nothing: say the list stays available and stop.
Two months in one selection. The user validates February and March 2026 on the period card. Step 6's result carries periods_requested: 2 and periods_covered naming both months, and no period_label on the envelope: name both months in the one line ("February 2026 and March 2026 — 19 suppliers with no invoice … €31,200 across both months."), take a month-by-month split from months only if the user asks for one, and leave the rows to the card — a supplier missing an invoice in both months holds one row per month. Step 8's preview keeps one agent per portal across the two months, and the recap names both months rather than one label.
A pick from the month before. The user picked five vendors for March, then says "actually, do April": step 4 writes April, step 6 renders April's card, and the March pick is not April's (rule 10). Ask for the tick and the Continue click on the April card, and preview nothing until it arrives — a wait-read here answers no_selection_yet, because the month change already dropped the March pick.
Text-only host. No card is drawn at step 6, so the rows are listed in text and the flow asks which vendors to chase. The user names three; match them against the listed rows, write the pick with well_switch_workspace({ workspace_id, counterparties }) from each row's company_id and matched_connector_service_id, then preview those three at step 8 and give the collect link instead of a Deploy pointer.
Tool unavailable. well_list_missing_invoices absent → step 6 is unavailable: say so; never approximate from raw transactions; stop and recap workspace, period, and coverage.