Files
accounted/extensions/general/mcp-server/skills/onboarding.ts
T
dae0aac26a fix(mcp): reconcile_match callable + two-phase efterkontroll + instant value for web-onboarded users (#1967)
* docs(plugin): starter prompt nudges the connector's onboarding guide

Several E2E runs composed the first reply from tool descriptions before
the skill loaded (questionnaire instead of the guided round). Four words
in the published prompt point the agent at the guide from message one;
the rest of the prompt stays memory-first and universal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(mcp): reconcile_match into the default catalog + two-phase efterkontroll + instant value for web-onboarded users

E2E #12 (the smoothest run yet) surfaced three things:

1. gnubok_reconcile_match was search-only, so Claude.ai could not call
   it: the agent misread the uncallable tool as a missing
   reconciliation:write scope and punted the entire dont-double-book
   matching step to the web app. Promoted to the default catalog
   (the standing rule: skill-instructed tools must be callable);
   ceiling 63K to 63.4K documented.

2. Skill: efterkontroll split into two phases per founder direction.
   Step 3b runs DIRECTLY after the import commits: verify the trial
   balance, sanity-read the content, and PREPARE the chart (create
   1630/8423/8314/6992 when the SIE lacks them) so the bank and
   Skatteverket connections land in a book that is ready for them.
   Step 4b after the connections: match SIE-covered bank rows with
   reconcile_match (never re-categorize), reconcile skattekontot to the
   öre, names, underlag, in a prioritized numbered list.

3. Skill: users who onboarded via the web app and connect the MCP with
   everything already set up get the reconciliation pass and Att
   göra-list as the FIRST reply instead of setup steps: immediate value
   regardless of onboarding path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 23:04:57 +02:00

257 lines
14 KiB
TypeScript

import type { Skill } from './types'
const body = `# Onboarding: set up a company in Accounted from the conversation
From "my company is not in Accounted yet" to a working ledger without the
user opening the web app first. The only browser steps are the ones that
legally need a human with BankID: connecting (creating the account),
approving the bank consent, and authorising Skatteverket. Everything else
happens here.
**Momentum rule: one confirmation, then keep going.** The only stop in this
flow is the create-company preview ("stämmer detta? ja"). Never end a turn
with "säg till när du vill fortsätta": after the confirm, call the connect
tools immediately; when the user reports a connection done, verify it and
continue straight into import or categorization. Staged writes still go
through their normal approval, but that approval IS the conversation, not an
extra pause around it.
**Brevity rule: this is onboarding, not a course.** Keep every reply under
~8 short lines. At most ONE warning per step, one line, and only when it
changes what the user should do right now. No legal essays, no deadline
tables, no cross-checks the user did not ask about (bolagsstämma dates,
EU-moms edge cases, K-regelverk). The user can always ask for depth; the
flow must never make them scroll past it.
**Memory first, memory back.** Before asking the opening questions, check
what you already know about the user (memory, earlier conversation):
orgnr, company name, bank, previous system. Ask only for what is
genuinely missing. AFTER the company is created, save the durable facts
to memory (orgnr, company name, bank, fiscal year, moms period, previous
system, that Accounted is set up): the user's next conversation should
need zero of these questions.
## When to use
- "Sätt upp bokföring för mitt AB / min enskilda firma"
- "Jag har precis startat bolag, hur kommer jag igång?"
- "Lägg till ett nytt bolag" (an existing user adding a second company)
- A byrå/consultant onboarding a new client company (pass \`team_id\`)
**Already set up? Deliver value immediately instead.** When
\`gnubok_get_agent_briefing\` shows a company that already exists with
data and connections (the user onboarded via the web app), do NOT walk
the setup steps: go straight to the Step 4b reconciliation pass and the
Att göra-list, and open with the findings: a numbered list with amounts
in your FIRST reply. That first impression is the product.
## Step 0: connect
If this session is not connected yet, the first company-scoped call (for
example \`gnubok_create_company\`) returns an authentication challenge that the
client shows as a Connect prompt. Tell the user: "Klicka på Connect; har du
inget konto skapar du det där (BankID eller e-post), det tar en minut." The
call is retried automatically once connected. Do not send the user to the web
app to sign up first.
## Step 1: THREE opening questions, then look up
Open with exactly three questions, together:
1. **Organisationsnummer?** (10 digits; an enskild firma's org number is the
owner's personnummer, fine to use here)
2. **Har du bokfört i ett annat system tidigare?** (Fortnox, Visma, Bokio,
Björn Lundén, Briox, Wint, annat system, eller helt nytt bolag)
3. **Vilken bank har företaget?** (so the bank connect link later opens that
bank's consent directly instead of a picker)
Never re-ask a question the user already answered in this conversation
(the opening round included): reuse the answer.
Then call \`gnubok_lookup_company\` with the org number. The registry answers
most of the form; present the facts as a SHORT summary to confirm ("Jag
hittade Example AB, Storgatan 1 i Stockholm, godkänd för F-skatt och
momsregistrerad. Stämmer det?") and ask ONLY what \`still_to_ask\` lists.
Never re-ask what the registry answered.
Rules baked into that split (same as the web onboarding):
- **F-skatt** from the registry is a fact, both true and false.
- **VAT** is a fact ONLY when positively registered. "No VAT registration
found" is a question, never an assumption (ML 17 kap 24 §).
- **Moms period** is ALWAYS the user's answer when VAT-registered. Rules of
thumb if unsure: under 1 MSEK turnover may report VAT yearly, under 40
MSEK quarterly, above monthly.
- **Accounting method is NOT a question**: it defaults by form (AB =
faktureringsmetoden, enskild firma = kontantmetoden) and the preview
flags the default. Name it in the readback ("faktureringsmetoden,
standard för AB, säg till om du vill ha kontantmetoden") so the user can
override in the same "ja". Cash requires turnover under 3 MSEK.
- **Enskild firma name**: verksamhetsnamnet is freely choosable; suggest the
registered name but let the user pick. An AB's registered name is a fact.
- **Fiscal year**: registry data becomes a confirm question, never an open
one. No closed period in the registry = FIRST räkenskapsår: suggest a
start at the registration date. The end differs by form: an enskild
firma MUST end 31 December; an AB may pick any end within 18 months of
the start (BFL 3 kap 3 §), with 31 December as the common default, so
present the AB's choice rather than assuming it.
\`not_found\`/\`unavailable\`: fall back to asking the \`still_to_ask\` list and
continue. Only \`aktiebolag\` and \`enskild_firma\` are supported today.
## Step 2: preview, ONE confirm, create, keep moving
Call \`gnubok_create_company\` WITHOUT \`confirm\`. Read the preview back in
plain Swedish (form, orgnr, fiscal period dates, VAT + period, method).
After the user's "ja": call it again with \`confirm: true\`, and in the SAME
turn continue with step 3 or 4. Creation sets up chart, settings, first
fiscal period, tax deadlines, and starts the 30-day trial; the connection
uses the new company automatically.
## Step 3 (existing bookkeeping): import it FIRST
When the user had a previous system, history comes before the bank: it is
the fastest path to a ledger that shows real value, and bank history rarely
reaches far enough back anyway.
1. Branch on WHICH system they name:
- **Fortnox / Björn Lundén / Briox / Wint** (API-connected systems):
offer TWO paths and recommend by need. The FULL migration: call
\`gnubok_connect_migration\` with the provider; it renders a connect
card (same feel as bank/Skatteverket) whose button opens the wizard
that logs into the old system and fetches every fiscal year PLUS
invoices, customers, suppliers and documents: recommend it when they
have open fakturor or want underlag along. The QUICK path is a SIE
export dropped here (Fortnox: Register → Exportera → SIE 4): ledger
only, fastest. Either way the result lands in the same books.
- **Visma eEkonomi / Bokio**: no API export exists; ask for the SIE
file (Visma: Bokföring → Export SIE; Bokio: Inställningar →
Exportera data → SIE) and use the drop card FIRST. Then
\`gnubok_connect_migration\` with the provider renders the card that
complements with invoices and customers.
- **Annat/okänt system**: every Swedish system exports SIE4
(.se/.sie); ask them to export it and drop it here.
2. As soon as SIE import is the next step, call
\`gnubok_create_sie_upload\`. On claude.ai/Desktop it renders a
DRAG-AND-DROP card: the user drops the file on it and the card itself
runs the preflight and stages the import with exact bytes; you only
narrate the verdict and handle the approval. Without the card: PUT the
raw bytes to \`upload_url\` (from your code sandbox), compute sha256,
and call \`gnubok_sie_preflight\` with \`upload_id\` + \`sha256\` +
\`filename\`; smaller files may go inline as \`file_content_base64\` +
\`sha256\`. NEVER reproduce a large file token by token: unhashed
oversized inline content is refused because a mid-verifikat truncation
imports silently incomplete bookkeeping.
3. THE CARD ACTS WITHOUT YOU SEEING IT: it stages the import and can
approve it too. When the user writes after a card was shown, your
FIRST call is \`gnubok_list_pending_operations\`: an empty ledger does
NOT mean the file never arrived; the import may be staged (approve it
on the user's word) or already booked. Without the card: summarize the
preflight in a few lines and stage \`gnubok_import_sie\` with the
preflight's \`mappings\`. After commit: \`gnubok_get_trial_balance\`
and \`gnubok_explain_voucher_gap\` for any skipped numbers (BFNAR
2013:2; unexplained gaps block year-end).
4. Multiple fiscal years = multiple files: import oldest first so IB/UB
chains. The web wizard at \`/import?mode=sie\` is the fallback when no
upload path works; Fortnox users can also run the full API migration
(invoices, customers, documents) at
\`/import?mode=migration&provider=fortnox\`.
## Step 4: connect bank and Skatteverket (together, no pause)
Call \`gnubok_connect_bank\` (pass \`bank\` from step 1 so the link opens that
bank's consent directly) AND \`gnubok_connect_skatteverket\` in the same
turn; on claude.ai/Desktop both render connect cards with buttons. When a
card rendered, do NOT paste the URL as text too: the card button IS the
link, and duplicate raw URLs read as clutter.
- Bank: BankID + PSD2 consent, then an **account selection dialog** in the
browser: transactions start syncing when the user saves it. Banks cap
PSD2 history (often ~90 days); older history is the SIE import's job.
- Skatteverket: BankID as firmatecknare; enables skattekonto sync and
moms/AGI filing. Optional, never block on it.
When the user says they are done (or comes back), re-call
\`gnubok_connect_bank\` to verify \`connected\`, then go DIRECTLY to step 5.
## Step 3b: DIRECTLY after the import: verify and PREPARE (before the bank)
The moment the import commits, run a fast pass so the connections land in
a book that is READY for them: the user should feel value before the bank
even connects.
- \`gnubok_get_trial_balance\`: balances, and matches the SIE's UB.
- Sanity-read the content: no income accounts? liabilities that look
already-paid? Say so in one line each; do not fix yet.
- PREPARE the chart for what the connections will bring: if the import
lacks 1630 (skattekonto), 8423 (kostnadsränta skattekonto), 8314
(skattefri intäktsränta) or 6992 (ej avdragsgilla avgifter), create
them NOW so tax payments and fees book correctly from the first sync.
- Voucher gaps: explain each with \`gnubok_explain_voucher_gap\`.
## Step 4b: after the connections: reconcile (this is where trust is won)
Once bank + Skatteverket deliver data, run the reconciliation pass and
fix findings through the normal staged flow, a few lines per finding:
- Bank rows covered by the SIE period: MATCH them against existing
verifikat with \`gnubok_reconcile_match\` (per account; \`dry_run\`
first): never categorize them again, that double-books salaries and
everything else. Only rows after the SIE's last date get booked fresh.
- Skattekonto vs 1630: reconcile the events against the ledger. Common
finds: paid payroll taxes still standing on 2710/2731, ränta/avgifter
unbooked (8423/8314/6992: never ordinary cost accounts, or the
year-end tax computation goes wrong). End state must match
Skatteverket's saldo to the öre.
- Auto-created bank accounts (1930/1931/1935) named after the company:
suggest proper names.
- Underlag coverage: verifikat over ~5 000 kr without documents (BFL 5
kap 6 §): list them, offer the receipt-matcher flow.
Present findings as a short numbered list with amounts, fix in priority
order on the user's go-ahead, and re-verify the reconciled balances match
external truth (skattekonto saldo, bank balance) to the krona.
## Step 5: first bookkeeping, immediately
Call \`gnubok_list_uncategorized_transactions\` as soon as the bank is
connected: do not ask whether to proceed. Walk the categorize flow
(\`gnubok_suggest_categories\`, \`gnubok_categorize_transaction\`, approval).
If nothing has synced yet, say so and check again on the user's next
message instead of making them ask.
## Tools
- \`gnubok_lookup_company\`: registry facts + prefill from the orgnr; call first
- \`gnubok_create_company\`: preview (no confirm) then create (confirm=true)
- \`gnubok_create_sie_upload\`: byte-exact upload URL for the SIE file
- \`gnubok_sie_preflight\`: scan a shared SIE file, nothing written
- \`gnubok_import_sie\`: staged import; use the preflight's mappings
- \`gnubok_connect_bank\` / \`gnubok_connect_skatteverket\`: status + connect links
- \`gnubok_connect_migration\`: connect card into the previous-system wizard
- \`gnubok_list_companies\`, \`gnubok_get_agent_briefing\`: state checks
- \`gnubok_list_uncategorized_transactions\`: the first real bookkeeping step
## Pitfalls
- A VAT-registered company without a moms period is refused on purpose; do
not work around it by claiming the company is not VAT-registered.
- Do not create a company twice on a retry: check \`gnubok_list_companies\`
if a create call was interrupted.
- A preflight org-number mismatch means the file is another company's
bookkeeping: stop and confirm, never import across companies.
- Bookkeeping duty starts when the company exists in Accounted with a fiscal
period. Never create a company "to try things out" for a real
organisation; use the sandbox in the web app for demos.
`
export const onboardingSkill: Skill = {
slug: 'onboarding',
name: 'Onboarding: New Company Setup',
summary:
'Set up a company in chat: orgnr + previous system first, prefill via gnubok_lookup_company, one confirm to create, SIE history via preflight + staged import, then bank/Skatteverket cards.',
tags: ['onboarding', 'setup', 'company', 'bank', 'skatteverket', 'sie', 'migration', 'agent-first'],
body,
tier: 'workflow',
}