Files
accounted/extensions/general/mcp-server/skills/onboarding.ts
T
cd9fb5b717 feat(mcp): byte-exact SIE upload path + brevity and memory-first onboarding (#1954)
From the fourth E2E run: the agent correctly refused to reproduce a
104 KB SIE file token by token (silent mid-verifikat truncation) and
dead-ended to the web wizard, and its replies were walls of compliance
prose.

1. gnubok_create_sie_upload: signed same-origin upload URL (reuses the
   pending-document infra; .se/.sie/.si only, 50 MB HTTP cap).
   gnubok_sie_preflight and gnubok_import_sie accept upload_id as the
   byte-exact source, plus optional sha256 (hex of the raw bytes)
   verified on the upload_id/base64 paths so truncation is DETECTED,
   never silent. Inline content above 120k chars is refused with a
   pointer to the upload flow. Scope bookkeeping:write (same intent as
   import_sie).

2. Skill: brevity rule (max ~8 short lines per reply, one warning per
   step, no legal essays), memory-first rule (check what is already
   known before asking the opening questions), the upload-first SIE
   step, and gnubok_explain_voucher_gap after import for skipped
   voucher numbers.

3. CONNECTORS.md starter prompt rewritten memory-first so it stays
   copy-paste ready without the user's own data in it. Plugin v1.2.2.

tools/list ceiling 62K to 62.4K documented in the bench.

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

188 lines
9.6 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.** 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.
## 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\`)
## 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)
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. Tell them where to export: **Fortnox** Register → Exportera → SIE 4,
**Visma eEkonomi** Bokföring → Export SIE, **Bokio** Inställningar →
Exportera data → SIE, **Björn Lundén / Briox / Wint** under Export.
Every Swedish system exports SIE4 (.se/.sie); ask them to attach the
file here in the chat.
2. When the file arrives, get its BYTES to the server without retyping
them. Preferred (and REQUIRED for anything beyond a small file): call
\`gnubok_create_sie_upload\`, PUT the raw file bytes to the returned
\`upload_url\` (from your code sandbox when you have one), compute the
file's sha256, then call \`gnubok_sie_preflight\` with \`upload_id\` +
\`sha256\` + the same \`filename\`. Small files may go inline
(\`file_content_base64\` + \`sha256\` preferred over plain
\`file_content\`). NEVER reproduce a large file token by token: the
tools refuse oversized inline content because a mid-verifikat
truncation imports silently incomplete bookkeeping.
3. Summarize the preflight in a few lines: source system, fiscal years,
verifikat count, balance status, org-number match, the one warning that
matters. On the user's go-ahead: \`gnubok_import_sie\` with the same
source (\`upload_id\` or content) and the preflight's \`mappings\`. It
stages for approval; after commit verify with
\`gnubok_get_trial_balance\`, and explain any skipped voucher numbers
with \`gnubok_explain_voucher_gap\` (BFNAR 2013:2; an unexplained gap
blocks 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.
- 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 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_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',
}