# Ar Aging And Collections

> Use the confirmed Mosofin workspace and its connected accounting datasource to pull live A/R, build and reconcile an aging, prioritize collections, calculate receivables metrics, and draft review-only reminders. Trigger for 'run an AR aging,' 'who owes us money,' 'prioritize collections,' 'calculate DSO,' or 'draft overdue-invoice reminders.' Workspace-scoped: require the Mosofin workspace and an active connection, discover which read-only tools are enabled, and resolve every task against that capability map before reading data. Do not use for cash application, bad-debt write-offs, invoice creation, or sending reminders.

- Skill: `mosofin/ar-aging-and-collections` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mosofin/ar-aging-and-collections`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mosofin/ar-aging-and-collections/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- Author: MosoFin (https://skillmd.com/u/mosofin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mosofin/ar-aging-and-collections

---


<!-- shared:onboarding-inline start -->
## Before you start — this skill works with or without Mosofin

**With Mosofin connected**, the skill reads your live accounting data through the
gateway: the figures come from your own books, it validates against the real chart of
accounts, and most steps run automatically.

**Without it, the skill still works.** No subscription, no connector, or a skill copied
on its own — you are not blocked and you are not asked to buy anything first. The
Mosofin gates are skipped and you are asked for what each step needs instead: a trial
balance, a statement, an export, the documents themselves. **The accounting logic, the
edge cases and the output standards are identical** — only where the numbers come from
changes, and the output always says which is which.

**You choose, and you are asked.** Where a connection exists, the skill asks at the
start whether to use it for this run or whether you would rather supply the data
yourself — **a connected gateway is not taken as consent to read your books.** Say no
and it runs manually without asking again.

### Strict rule — this skill never changes your data

**This skill will never write, update or delete existing data in any data source.**
Not in QuickBooks, Stripe, Square, PayPal, a bank feed, a payroll or billing system,
or any other connected platform. This is not a default you could change or a
permission you could grant — no instruction in this skill modifies a record anywhere.

It will **never**:

- create, edit, overwrite, void or delete a record in a connected platform
- invoke a write operation, or ask you to approve or enable one — a write tool is out
  of scope even when your policy has it enabled
- direct you to update, overwrite or delete existing data in a data source
- copy or move data from one connected platform into another

What it does instead is **read, and propose.** Every entry, schedule, reconciliation
and document it produces is a **draft for you to review.** Where it finds a problem —
a duplicate, a mismatch, a stale balance — it describes the problem and proposes a
correcting entry as a draft. It does not tell you to delete or overwrite the original,
and it never acts on one itself.

Whether anything reaches your books is a decision you make outside this skill, in your
own system, by your own hand. **If you act on none of it, nothing in your data has
changed.**

### Onboarding — required whenever Mosofin is connected

**If the Mosofin gateway is connected, onboarding is not optional and not per-skill.**
Before any skill reads anything, the workspace and the data sources in it must be
confirmed with you. It is the same sequence for every Mosofin skill, so it is kept in
one place rather than repeated in each:

- in this repo: [`shared/onboarding.md`](../../shared/onboarding.md)
- installed on its own, or you would rather read the product docs:
  [docs.mosofin.com/start-here/quickstart](https://docs.mosofin.com/start-here/quickstart)

**Already onboarded this workspace?** Then you have answered it once and will not be
asked again from scratch — but **the confirmation itself still happens every run.**
Gate 0 reads your workspace back and waits for an explicit yes; Gate 1 settles which
company file. Those are not skippable, and no data is read before them.

**No Mosofin connector? The skill still works.** If the gateway is not present at all,
there is nothing to onboard: the gates are skipped, every step becomes `[manual]`, and
you are asked for what each step needs — a trial balance, a statement, an export.
**The accounting work is unchanged**; only the data source is. You will be told this
once, and you will not be asked to install anything before being helped.

What follows in **Part A** is not more onboarding. It is this skill exploring what your
confirmed workspace and data sources can actually do — which tools exist, which serve
this particular request — so the run is shaped around your books rather than a generic
template.

---
<!-- shared:onboarding-inline end -->
# A/R Aging and Collections (Mosofin)

Builds an **accounts receivable (A/R) aging**, reconciles it, prioritizes collections, computes
receivables metrics, and drafts review-only reminders.

**In plain words:** this answers *who owes us money, how late are they, and who should we
chase first?* An **aging** sorts every unpaid customer invoice by how overdue it is.
**Collections** is the work of getting that money in.

Use this skill only after the Mosofin workspace and the company file are confirmed. Pull live
data through the Mosofin datasource gateway, then ask once for material fields the connector
cannot supply. **Never write to the accounting system and never contact customers.**

**Mosofin is read-only.** It cannot create an invoice, apply a payment, write off a balance,
place a credit hold, or send a reminder. Every draft below is text for a human to review and
send themselves.

---

# ONBOARDING — Confirm the workspace and its data sources

**Required for every skill, every run — whenever Mosofin is connected.** Gates 0 and 1
settle *which books this is about*: the workspace, and the data sources inside it.
**Part A then explores what those confirmed sources can actually do** and personalises
the run around them. Nothing is read before Gate 0 is answered.

**If the Mosofin tools are not present at all, skip this part.** There is nothing to
onboard: say so once, then run the skill manually on data the user supplies. See the
precondition check below.

Run Gates 0 → 1 → 2 → 3 in this order, before any data read. This ordering is the contract.
Do not skip a gate because a previous conversation covered it — connections, permissions, and
company files change between runs.

Call the Mosofin tools by the **bare names your own tool list exposes** — `list_workspaces`,
`get_agent_datasources`, `get_datasource_tools`, `invoke_datasource_api_tool`, `get_skills`,
`get_my_skill`, `create_skill`. Do not add a `mosofin_` prefix and do not hardcode a
client-side `mcp__…` namespace; that string is composed by whichever MCP client is running.

<!-- shared:scope-protocol start -->
### First — ask whether to use Mosofin for this run

Two things decide how this skill runs, and they are settled **before Gate 0**.

**1. Are the Mosofin tools present at all?** — `list_workspaces` and the rest of the
gateway. Check before doing anything else.

**2. If they are present, ask the user. Once, in these terms:**

> Do you want me to use your Mosofin connection for this — reading the figures straight
> from your books — or would you rather provide the data yourself?

**Wait for the answer.** A connected gateway is **not** consent to read from it, and
this skill does not open with a data read. Never assume, never auto-pick.

- **Use Mosofin** → onboarding is required. Run Gates 0-1 to confirm the workspace and
  its data sources, then Part A explores what those sources expose.
- **Provide the data myself** → **skip Gates 0-2 entirely** and run manually, exactly as
  though no connector were present. **Do not ask again during the run.** Raise it once
  more only if the user asks for something their supplied data cannot answer, and then
  as an offer, not a demand.

**If the tools are not present, do not ask** — there is nothing to choose. The skill was
copied on its own, the connector was never added, or there is no subscription.
**Do not make connecting a condition of helping.** Say once, plainly, that Mosofin is
not connected and this run will be manual, then **carry on with the skill's normal
workflow**: ask for what each step needs — a trial balance, a statement, an export, the
documents themselves — and do the accounting work on what the user provides.

**In manual mode**, whether chosen or unavoidable:

- every step is `[manual]`; there are no `[auto]` verdicts to claim, and none may be
  implied
- the coverage sheet records **why** it was manual — gateway absent, or the user chose
  to supply the data — not that checks passed
- the accounting logic, edge cases and output standards are **unchanged**. That is the
  part of this skill that never depended on a connection
- mention **once** that connecting Mosofin would automate the manual steps, with a link
  to [docs.mosofin.com](https://docs.mosofin.com). Do not raise it again, and never
  withhold work to press the point

#### What a manual run actually does, gate by gate

| Gate | In a manual run |
|---|---|
| **Gate 0** — workspace | **Skipped.** There is no workspace to confirm. |
| **Gate 1** — data sources | **Skipped as a discovery step.** Still ask *which entity or company this work is for*, by name, so every output can be labelled — but record it as **user-asserted**, not confirmed against a connection. |
| **Gate 2** — capability map | **Skipped.** The map is not empty, it is uniform: **every task is `[manual]`.** |
| **Gate 3** — profile, then interview | **Runs, and grows.** The profile half cannot run — there is no company-profile tool — so everything it would have derived silently becomes a **question**: base currency, fiscal calendar, country or region, time zone. Then the interview runs in full, and **every row of the Inputs table that would have been `[auto]` becomes something to ask for.** |

Then **Part B runs unchanged** on what the user supplied.

**Ask the user to upload the data, and name the formats.** A manual run does not mean
retyping anything. Say plainly what to upload, in what form, and what each item is for —
then read it from the files they provide.

| Ask for | Upload as |
|---|---|
| Ledger detail, trial balance, transaction listings | **CSV** or **XLSX** export, or a pasted table |
| Statements and third-party documents | **PDF** or **CSV**, or a clear photo / scan |
| Invoices, bills, receipts, remittances | **PDF** or **image** — a single file or a batch |
| Short facts — a date, a balance, a policy | typed straight into the chat |

**Ask for the whole set up front, as a checklist, not drip-fed.** A person collecting
exports would rather be given one list than be interrupted six times. Mark which items
are strictly **required** and which merely improve the result, so they can decide how
much to gather.

**Confirm what actually arrived before starting the work.** Name each file, say what was
read from it — period covered, row count, opening and closing balances — and list what
is still outstanding. If a file is unreadable, covers the wrong period, or does not
contain what its name suggests, **say so at once**. Never work around a bad input
silently, and never guess at a column you cannot identify — ask.

**If something cannot be supplied, say what the output will and will not be — before
doing the work.** Never estimate a figure that was meant to come from the books, never
fill a gap with a plausible number, and never present a partial result as complete. An
honest partial answer, clearly labelled, is the correct outcome.

**Everything the user provides is evidence like any other.** Reconcile it, check it,
and challenge it where it does not tie. Manual input is not more trustworthy than a
ledger read — it is less, because nothing validated it on the way in.

**Present but not authenticated is not the same as absent.** If the tools are there and
a call returns a `reconnect_url` or an auth error, surface it and let the user choose —
reconnect, or continue manually. Do not silently fall back.

### Confirming scope — workspace, then data sources, then tools

**Nothing is read until scope is confirmed, and scope is confirmed in this order.**
Each step depends on the answer to the one before it, so none of them may be skipped,
merged, or guessed at.

| # | Question to the user | How it is settled |
|---|---|---|
| 1 | **Which workspace?** | `list_workspaces` with **no arguments**. Read the workspace back **by name** and wait for an explicit yes. On `selection_required`, ask whether this is single- or multi-workspace, then which **by name**, then call again with `workspace_ids=[…]` and `mode="single"`/`"multi"`. |
| 2 | **Which data sources, in that workspace?** | `get_agent_datasources` with the confirmed `workspace_id`. `connected: true` is in scope; `connected: false` is **excluded and named as excluded**, with any `reconnect_url` surfaced. Then settle the entity scenario — single-entity: which company; multi-entity: which set — always by `display_name`. |
| 3 | **Which tools do those sources actually expose?** | `get_datasource_tools` per in-scope datasource, **and per company file when several are live — permissions are per company.** This is discovery, not a question: read what is there before promising anything. |

**Never auto-pick.** Not the workspace, not the company file, not the entity scenario.
**Silence is not a yes**, and an answer to one question is not an answer to the next.

**Names, never internal ids.** Name the workspace and refer to companies by
`display_name`. Never print an internal numeric tenant id, and never show a raw
`data_source_id` — pass the opaque handle, show the name.

**Only then does the work begin.** Once the workspace, the data sources and their tools
are confirmed, resolve every task against what was actually found: what is available
now decides which steps are `[auto]`, which are `[gated]` and which fall to `[manual]`.
Where the confirmed tools cannot answer the request, say so and ask — do not substitute
an assumption for a capability.

**The catalogue is authoritative.** Take exact `tool_name` values from the Gate 2
listing — names are not uniformly styled, some underscored, some hyphenated.
**Do not invent a tool name.** On `UNKNOWN_TOOL`, read the valid names from the error
and retry.
**Never call a tool whose `effective_policy` is `disabled`.**

**This map is built fresh every run** and held only for this run. It is written out in
the coverage sheet, never written back into this file.
<!-- shared:scope-protocol end -->

## Gate 0 — Confirm the workspace

1. Call `list_workspaces` with **no arguments**.
2. If **multiple** workspaces are returned, ask whether this is a single- or multi-workspace
   task, ask which workspace(s) **by name**, confirm them with `workspace_ids=[…]` and
   `mode="single"` / `mode="multi"`, and use only the returned opaque `ws_…` handle(s).
   **Never expose internal numeric IDs.**
3. If **one** workspace is returned, read its name back and **wait for explicit user
   confirmation** before continuing.

## Gate 1 — Discover live datasources and settle the entity scenario

4. Call `get_agent_datasources` for the confirmed workspace. Require an **ACTIVE** datasource
   (`connected: true`). Rows with `connected: false` are **excluded and named as excluded** —
   an aging that silently omits an entity understates what the group is owed.
5. If **multiple company files are active**, ask the user to choose by `display_name`. Pass
   the chosen `data_source_id` on every subsequent Mosofin call; **never show that ID**.

**If the datasource is disconnected, expired, or not ACTIVE, stop.** Show the Mosofin
`reconnect_url`, tell the user to reconnect in Mosofin, and wait for confirmation before
retrying. **Do not substitute CSVs or guessed numbers while the user is asking for live
Mosofin data.**

Settle the entity scenario:

- **Single-entity** — one company by `display_name`; the workflow runs against that one
  `data_source_id`.
- **Multi-entity** — ask which set. The workflow runs **once per entity**, every call
  targeting exactly one `data_source_id`, and every aging row, metric and draft carries its
  entity's `display_name`. Two entities' receivables are never blended into one figure
  without that label — and DSO in particular is meaningless when blended, because each
  entity has its own sales base.

# PART A — Explore the confirmed sources, and personalise this run

The workspace and its data sources are settled. This part finds out **what they
expose and which of it serves this request** — the tool catalogue in Gate 2, then
what is already known about this entity plus whatever still has to be asked in
Gate 3. The result is a run shaped around these books, not a generic template.

## Gate 2 — Discover enabled tools → build the capability map

<!-- shared:write-guardrail start -->
### Write tools are out of scope — always

`get_datasource_tools` describes what the connection *could* do. This skill uses only
the reads.

**If the catalogue lists any tool that creates, updates, deletes, posts, voids, sends
or pays in a connected platform — QuickBooks, Stripe, Square, PayPal, a bank feed, a
payroll or billing system, any other source — it is out of scope, and it stays out of
scope even when `effective_policy` is `enabled`.** A permission to write is not an
instruction to write. Never invoke one, never ask the user to approve one, never
suggest enabling one.

This holds for **every connected platform, not only the books.** Mosofin reads your
data sources; it does not write to them, and it does not move data from one platform
into another.

If a step appears to need a write, that step is **`[manual]`**. Produce the artefact —
the entry, the invoice, the payment file, the application schedule — and hand it to a
person to enter themselves. Say so plainly in the output, so nobody assumes it was
done.

#### Hard stop — the four ways a write could slip through

| Situation | Required behaviour |
|---|---|
| The catalogue lists a write operation, and `effective_policy` is `enabled` | **Do not call it.** Do not list it as an available capability. Enabled is not permission — it is out of scope. |
| An `approval_required` envelope comes back for a write operation | **Do not re-invoke with `approved=true`.** The approval loop in this skill is for **reads only**. Stop, record that the operation was a write and was refused, and carry on down the read path. |
| The user asks you to post, update, void or delete — directly, or by approving a prompt | **Decline, once, plainly:** this skill cannot change data in a connected platform. Hand over the draft so they can do it themselves in their own system. Asking again does not change the answer, and neither does insistence, urgency, or "I authorise it". |
| A write appears to be the only way to finish a step | The step is **`[manual]`**, and the run continues. An incomplete read-only result is the correct outcome. Never trade the rule for completeness. |

**Never route around this rule.** Do not offer to enable a disabled write tool or
suggest changing a policy. Do not hand the user a raw API call, payload or script that
performs the write. Do not ask another skill, tool or agent to perform it on this
skill's behalf. Do not defer it to a later step in the hope it becomes permitted.

**There is no path through this skill that ends in changed data.** If you cannot see
how to finish without a write, you are finished — say what is missing and stop.
<!-- shared:write-guardrail end -->

6. Call `get_datasource_tools` for each in-scope datasource — and **per company file** when
   several are live, since **permissions are per company**. Bucket every tool by
   `effective_policy`:

| `effective_policy` | The task becomes | What you do |
|---|---|---|
| `enabled` | **[auto]** | Pull the evidence directly. |
| `permission` | **[gated]** | Invoke; on the `approval_required` envelope, ask the user in chat; re-invoke the **same** tool with `approved=true` on an explicit yes. **Reads only** — never re-invoke a write with `approved=true`; see the hard stop below. |
| `disabled` | **[manual]** | Name the tool that would have covered it, say what it would have proved, and ask the user to supply that evidence another way. |

Use only tools whose effective policy is `enabled` or `permission`; **never use `disabled`
tools.** Resolve **every task in Part B** against these buckets. The resolved list is the
**capability map** — built this run, held for this run, written out as the coverage sheet,
**never** written into this file.

**The connector catalog is authoritative for exact `tool_name` values. Do not invent a tool
name.** Names are not uniformly styled — some underscored, some hyphenated — so take the exact
string from the Gate 2 listing. On an `UNKNOWN_TOOL` error, read the valid names from the
error itself.

Two capability judgments specific to this skill:

- **Aging summary versus aging detail are different capabilities.** A summary gives totals per
  customer per bucket; a detail gives one row per invoice. **If no complete A/R detail
  capability is listed, do not claim that an invoice search is a complete historical aging
  population** — most invoice searches default to a recent date range and will silently omit
  old unpaid invoices, which are precisely the ones collections cares about. Request the
  smallest equivalent export for the tie-out instead.
- **A customer balance report is not an aging.** It gives one total per customer with no
  due-date buckets, so it cannot say what is overdue or by how long. Never present it as an
  aging. Where the aging report is unavailable but invoice-level data is complete, you may
  **reconstruct** the aging from invoices, payments and credits — label it as reconstructed
  and tie it back.

7. Route every data read through `invoke_datasource_api_tool` with the confirmed
   `workspace_id` and the selected `data_source_id` when required. Reads are live and
   read-only; **check each response's `mock` field and say when a result is fixture data.**

## Gate 3 — Profile the entity, then interview the user

Call the platform's company-profile tool (on QuickBooks, `get_company_info`).

**Derive silently** what the profile answers — and confirm the company before reporting:

- Company name and address — **confirm the company before reporting**; if it is not the
  expected one, ask the user to reconnect or select the correct company
- Base / functional currency
- Fiscal calendar and year-end, time zone

**Ask the user**, as **one consolidated question**, only for missing inputs that change the
requested output. See Step 4 — core aging does not require dispute flags, payment history,
DSO, or collection policy.

### Input plan

| Input | Typical read | Rule | If unavailable |
|---|---|---|---|
| **Company** | Company profile / datasource connection | Confirm company before reporting | Ask user to reconnect or select the correct company |
| **As-of date** | User request or current date | Pass a concrete `YYYY-MM-DD` date | Ask only if ambiguous. **Never silently default it** |
| **Open invoices** | A/R aging **detail** capability if exposed; otherwise the catalog's invoice search / report | Prefer a complete as-of report; **do not rely on a recent-only invoice query** | Ask for an A/R Aging Detail export after explaining the missing connector capability |
| **Independent totals** | A/R aging **summary** capability | Pull unfiltered for the same date | Derive from detail and **disclose that there is no independent tie-out** |
| **Credits / payments** | A/R detail without a transaction-type filter, or payment / credit search tools | Pull only when invoice detail does not reconcile | Ask for unapplied credits / payments or a full detail export |
| **Invoice enrichment** | Invoice search / single-invoice read | Query selected IDs or document numbers only | Leave unavailable enrichment fields blank |
| **Currency** | Report / invoice response | **Preserve each currency separately** | Ask before presenting unlabeled consolidated totals |
| **Exact DSO denominator** | Credit sales and period | **Require explicit credit sales** | Ask the user; offer a labeled total-income proxy only with approval |
| **Disputes, credit holds, strategic tier, payment history** | Not reliably exposed by aging reads — **[manual]** | **Never infer** | Ask only when needed for the requested prioritization |
| **Collection policy / brand voice** | User-provided policy — **[manual]** | Apply only supplied terms | Use neutral language without fees or threats |
| **Confirm scope** | — | Read back in-scope and excluded company files by `display_name` | — |
| **Confirm manual evidence** | — | For each `[manual]` gap, ask whether the user can supply it, and how | — |

**On later runs**, read stored preferences first (Step 7), confirm them in one line, and ask
only what is new, changed, or contradicted. Strategic tiers, collection policy and brand voice
in particular should not be re-asked every cycle.

## Guardrails

- **Never** create / update records, apply payments, write off balances, or send reminders.
- **Mark all correspondence `REVIEW REQUIRED - NOT SENT`.**
- **Do not invent** disputes, credit holds, payment promises, late fees, service suspension,
  or legal action.
- **The A/R aging may be accrual-only in the available connector.** If cash basis is
  requested, explain the limitation and **ask whether accrual basis is acceptable before
  reading data** — do not read first and caveat afterwards.
- End a live-data response with **one `Data sources` line** naming the Mosofin calls used and
  their retrieval time.

---

# PART B — The domain work

Every step below is the original procedure, unchanged in count, order, or substance, with
plain-language wording, an `[auto]` / `[gated]` / `[manual]` verdict, and the typical evidence
tool added.

**Never drop a task because no tool covers it.** A `[manual]` task is a task with a *named
gap*, not an absence.

Tool names in *italics* are typical. Resolve real names and policies from your Gate 2 catalog.

## 1. Pull and confirm

**Batch independent reads into one message** — the aging summary, the aging detail, and any
enrichment query do not depend on each other. Never serialize them. The server is
**stateless**: pass `data_source_id` on **every** call, including retries.

1. **Resolve the Mosofin workspace and company file** using Gates 0–1 above.
2. **Discover or match the A/R aging summary capability** — semantically equivalent to an
   "Aged Receivables" report — **[auto]** (*`get_aged_receivables`*).
3. **Discover or match the A/R aging detail capability** — semantically equivalent to an
   "Aged Receivable Detail" report. **[auto] if listed; [manual] if not** — in which case
   request the export named in the input plan.
4. **Invoke detail for the chosen date**, with invoice-only filtering when supported.
5. **Invoke unfiltered summary for the same date** — this is the independent total used in
   Step 3.
6. **Use invoice search only for selected enrichment** (*`search_invoices`*). **Its
   recent-date default can omit old unpaid invoices**, so it is an enrichment tool here, never
   the population.

**For one customer, use an exact customer name.** Resolve a fuzzy name before pulling the
report (*`search_customers`*, **[auto]**) and **state the match** you resolved to — a short trading name
matching to a longer registered legal name is a judgment the user should see.

## 2. Normalize

Create **one row per invoice**:

| Customer | Customer ID | Invoice # | Invoice ID | Invoice Date | Due Date | Days Past Due | Bucket | Original | Open Balance | Currency | Source |
|---|---|---|---|---|---:|---|---:|---:|---|---|---|

- **`Days Past Due = as-of date − due date`**; zero or less is **Current**.
- **Use open balance, not original amount, for aging totals.** The original amount is what was
  invoiced; the open balance is what is still owed after payments and credits.
- **Preserve IDs / document numbers as text** — leading zeros and prefixes are lost if they are
  treated as numbers.
- **Leave absent fields blank; do not fabricate them.**
- **Set Source to the exact Mosofin tool used**, or to the user-supplied fallback.

**Default buckets: Current, 1-30, 31-60, 61-90, 91-120, Over 120.**

**A summary report may return a single `91+` bucket; split it using due dates from the
detail.** If detail is unavailable, say the 91+ bucket could not be split rather than
presenting an unsupported breakdown.

## 3. Reconcile

**Compare invoice-detail totals with the A/R aging summary, by customer and in total** —
**[auto]** where both capabilities exist.

- **Combine 91-120 and Over 120 before comparing** with a summary's single `91+` bucket.
- **If totals differ, pull detail again without invoice-only filtering** and identify the
  reconciling rows — credits, payments, journal entries, or other transaction types.
- **Use a 0.01 currency-unit tolerance.**
- **Disclose unresolved variance; never force a tie.**
- **Call this a summary-to-detail reconciliation unless a GL control balance was separately
  tested.** The **general ledger control balance** is the single A/R total in the books
  (*`get_balance_sheet`*, **[auto]**); testing against it is a stronger check and deserves the
  stronger name only when it was actually done.

## 4. Ask for material gaps

After pulling the data, **ask one consolidated question** — only for missing inputs that change
the requested output. **Core aging does not require dispute flags, payment history, DSO, or
collection policy; omit and disclose these when not requested.**

Example:

> The connected books supplied the aging and balances but not reliable dispute, credit-hold,
> strategic-tier, or collection-policy fields. Please provide any relevant flags and approved
> escalation wording. For exact DSO, also provide credit sales and the measurement period.

## 5. Analyze

Calculate when supported — **[auto]** arithmetic over the pulled data:

- **Total A/R, overdue total, overdue percentage, and amount / percentage over 90 days**
- **Weighted average days past due** — weighting by open balance and **flooring current
  invoices at zero**, so invoices paid early do not mask late ones
- **Top-five customer concentration** — how much of the total is owed by the largest five.
  Concentration is a risk measure: one customer at 40% of A/R is a different business from
  fifty at 2% each
- **`DSO = ending A/R / credit sales for period × days in period`** — **days sales
  outstanding**, the average number of days it takes to get paid

**If total accrual-basis income substitutes for credit sales, label it `Approximate DSO
(total-income proxy)`** — **[gated]**: total income (*`get_profit_and_loss`*, **[auto]**)
includes cash sales that were never receivable, so the proxy flatters the number. Offer it
only with the user's approval, and never present it unlabeled.

**Rank overdue customers primarily by age and open balance** — **[auto]**. **Use payment
history, disputes, credit holds, or strategic status only when supplied** — **[manual]**;
never infer them from the ledger. **Route disputes to resolution rather than collection
pressure**: chasing a disputed invoice as though it were simply late damages the relationship
and does not get it paid.

**Suggested action bands:**

- **1-30** — friendly reminder
- **31-60** — formal follow-up / payment-date request
- **61-90** — management review / direct contact
- **Over 90** — escalation review under the user's approved policy

**Bankruptcy or insolvency notices stop ordinary dunning and require professional review.**
*Dunning* is the routine sequence of payment reminders; continuing it after an insolvency
notice can breach a legal stay and expose the entity.

## 6. Deliver

Return the requested subset, or a workbook containing:

1. **Run Context** — workspace and company by name, as-of date, accounting basis, extraction
   time, sources, missing fields. **Add:** each in-scope company file by `display_name`, each
   excluded one and why, and whether any figure rests on `mock` data
2. **Aging Summary** — customers, buckets, totals, metrics, next action
3. **Aging Detail** — normalized invoice rows
4. **Collection Plan** — ranking, reason, owner / status blanks, next action
5. **Drafts** — review-only reminder text, every one marked `REVIEW REQUIRED - NOT SENT`
6. **Exceptions** — credits, missing dates / currency, disputes, and reconciliation variance
7. **Coverage — NEW, Mosofin-specific** — one row per task in Steps 1–5:

| Task | Entity (`display_name`) | Verdict (auto / gated / manual) | Tool used | Policy (enabled / permission / disabled) | `mock` | Gap — what could not be verified and what the user must supply |

Rows for disputes, credit holds, strategic tier and payment history will read `manual` with
their gaps named. That is the correct, expected result — not a defect.

If creating XLSX, use the available spreadsheet skill and name it
`QuickBooks_AR_Aging_Collections_YYYY-MM-DD.xlsx` — or, where the datasource is not
QuickBooks, `[Datasource]_AR_Aging_Collections_YYYY-MM-DD.xlsx`. In a multi-entity run,
include the entity: `…_AR_Aging_Collections_YYYY-MM-DD_[EntityDisplayName].xlsx`, plus one
combined file. Every file states which datasource and `display_name` it covers.

If creating xlsx, read first: `/mnt/skills/public/xlsx/SKILL.md`

## 7. Evolve the skill (Mosofin-specific, final step)

**The file you installed is a seed.** After the user has **seen the results** and approved
them, ask — explicitly, at that point, not earlier — whether to save this as their own
customized version. A general "yes, go ahead" from earlier does not count.

On an explicit yes, persist the **decisions**:

- **Strategic tiers per customer**, and any standing credit holds with the reason and date
  opened — pure judgment the books never hold, and the thing that otherwise gets re-asked
  every cycle
- **The collection policy and brand voice**: approved wording, the payment contact address,
  the escalation ladder, and what must never be said (late fees, legal action, suspension)
- **The action bands**, if the user overrode the defaults
- **The DSO basis** — whether exact credit sales are available and where they come from, or
  whether the labeled proxy was approved
- **Bucket definitions**, if non-default, and whether the 91+ split is available
- Standing disputes and their owners
- The replay recipe: the exact sequence of reads that produced this aging

Save via `create_skill` — bundle `SKILL.md`, `references/run-recipe.json`, and the preference
files; set `datasources=` to match the recipe; no `.html`, `.css`, or `.svg` files. Or write
preference files alongside the installed skill.

**Do not persist customer contact details, outstanding balances, or draft correspondence.**
Those are a snapshot of one moment and personal data besides; persist the *policy and the
tiers*, never the ledger.

**Key every preference and asset by datasource + entity `display_name`.** Write "quickbooks /
Northbrook Trading — Customer A: strategic tier A, escalation owner Kim" — not "Customer A:
tier A". The same customer can be strategic to one entity and marginal to another, and an
unlabeled tier applied to the wrong company file misdirects a real collections effort. Record
the chosen **scenario** (single vs multi, and which set) as a preference too.

**Never persist state.** Connections, company files, tool policies, and `mock` status belong
to the workspace and are re-discovered by Gates 1–2 every run. **Decisions are the user's;
state is the workspace's.**

On later runs, match stored entity names against Gate 1's live list. An entity in preferences
that is no longer connected is **flagged** — never silently dropped, never applied elsewhere.

---

## Both entity scenarios

**Single-entity.** The workflow above against one `data_source_id`. One aging, one collection
plan, one workbook.

**Multi-entity.** Steps 1–6 run **once per entity**, each call targeting exactly one
`data_source_id`, every aging row, metric and draft carrying that entity's `display_name`.
Then one cross-entity step:

- **A side-by-side comparison plus a group total for A/R.** Total receivables aggregate
  meaningfully; **DSO does not** — each entity has its own sales base, so a blended DSO is a
  number with no denominator. Report DSO per entity, and a group DSO only if group credit
  sales were supplied.
- **Shared customers are the point of the comparison.** One customer owing three entities may
  be a concentration risk invisible at each one alone, may warrant a single coordinated
  approach rather than three separate reminders, and may be disputed at one entity while being
  chased at another — which is exactly how a relationship gets damaged.
- **Intercompany receivables** — where one entity owes another — are excluded from collections
  and routed to `intercompany-reconciliation`.

Capability is checked **per entity** at Gate 2: the aging detail capability may be listed for
one company file and not its sibling, so the same task can be `[auto]` for one and `[manual]`
for another. The coverage sheet shows this per entity.

---

## Mosofin read-call reference

Use the logical Mosofin gateway below; **exact tool names come from `get_datasource_tools`**:

```json
{
  "datasource": "<datasource id, e.g. quickbooks>",
  "tool_name": "<catalog tool name>",
  "workspace_id": "<confirmed ws_... handle>",
  "data_source_id": "<selected company file id when required>",
  "params": {"report_date": "YYYY-MM-DD"}
}
```

Typical connector semantics — **verify each against your Gate 2 catalog; policies and
availability vary by workspace and by company file**:

- **`get_aged_receivables`** — as-of A/R aging summary; may be accrual-only. Key arguments:
  `report_date`, `customer`, `aging_method` (Current / Report_Date), `days_per_aging_period`,
  `num_periods`.
- **An A/R aging detail capability, when listed** — invoice-level customer, due date,
  transaction type, and open amount.
- **`search_invoices`** — selected invoice enrichment; **requires `start_date` and `end_date`**
  for transaction searches, which is exactly why it is not a complete aging population.
- **`read_invoice`** — one invoice in full. Key argument: `id`.
- **`search_payments`, `search_credit_memos`, `search_refund_receipts`** — reconciliation
  diagnostics. All require `start_date` and `end_date`.
- **`get_customer_balance`** — total owed per customer. Useful for a tie-out; **not an aging**.
- **`get_customer_sales`** — sales by customer, for concentration and for a credit-sales basis.
- **`search_customers` / `get_customer`** — customer master, for fuzzy-name resolution and
  contact fields.
- **`get_balance_sheet`** — the A/R control balance, for a true GL tie-out.
- **`get_general_ledger`** — transaction detail behind the A/R balance.
- **`get_profit_and_loss`** — **requires `start_date` and `end_date`**; use only for an
  approved approximate DSO proxy.
- **`get_company_info`** — company confirmation, base currency, fiscal calendar.

Each tool's **own description in your Gate 2 catalog is the authority** on its arguments and
failure envelopes. Where this reference and the live description disagree, the live
description wins.

**Mosofin is strictly read-only. Do not call any send-reminder, create, update,
payment-application, or write-off capability.** None is exposed; do not look for one.

Envelope handling: `approval_required` → ask in chat, re-invoke the same tool with
`approved=true`. `entity_required` → ask by `display_name`, then pass that `data_source_id`.
`tool_policy_disabled` → convert to `[manual]` and record the gap. Dead connection → surface
the `reconnect_url`.

---

## Validation scenarios

*The original carried a concrete demo fixture — one company's name, customers, invoice numbers,
balances and a fixed aging date. That has been generalized here: a seed installs into **any**
workspace, and baking one workspace's data into it is the exact anti-pattern the gates exist to
prevent. **Every scenario the fixture encoded is preserved below**, as conditions and expected
behaviours. A user who wants a concrete fixture with their own figures should build it into
their **evolved** skill at Step 7, seeded through a separate approved sandbox process — this
skill must never seed records itself.*

Each row is a case the aging must handle correctly. Use them to sanity-check a run.

| # | Scenario | Expected behaviour |
|---|---|---

…(truncated)
