Install
$ agentstack add skill-cyanxxy-nl-tax-agent-skills-nl-tax-intake ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
NL Tax Intake
Open the conversation with the user, figure out which Dutch tax workflow applies, and progressively build a taxpayer profile. This skill is conversational. The user does not arrive with everything ready - ask one focused thing at a time, persist the answer, and continue.
User-facing boundary
Keep setup invisible. Do not narrate internal setup steps such as skill selection, rule or template loading, path resolution, local state-file creation, YAML updates, or policy loading. Create and update local files silently. Do not proactively recite generic warnings. The first user-facing reply should say only that you prepare a local workpack and then ask the screening questions. If the workflow is already clear and the user has documents ready, ask them to upload the relevant tax files; do not ask them to upload workspace or state files.
When to use
- User wants to file a Dutch income tax return
- User wants to request, change, review, or stop a voorlopige aanslag
- User mentions Dutch taxes, belastingaangifte, aangifte, or voorlopige aanslag
- First contact for any Dutch tax preparation task
Read first (every turn)
Bundled paths below are relative to this skill's own directory: templates/ is a subfolder, and _shared/ is the plugin-shared folder at ../_shared/. Resolve bundled files with host file tools (Read first, Glob or Grep if a path is not obvious). Do not use Bash to discover or read plugin files: in Cowork, shell commands run in an isolated VM that may not see the plugin cache even when Read and Glob can. If the host has already expanded ${CLAUDE_PLUGIN_ROOT} or ${CLAUDE_SKILL_DIR}, those absolute paths are fine for file tools; otherwise search within the loaded plugin/skill tree and resolve relative to this skill directory.
Before responding to the user, read:
_shared/knowledge/methods/interactive-elicitation.md- the conversational contract this skill follows.reference/filing-paths.md- intent-based workflow disambiguation and the request / change / review / stopzetten decision tree. Use it whenever the user is unsure which workflow they want, or does not recognize the jargon in screening question 4.workspace/shared/session-progress.yamlif it exists. If it does not, copy_shared/templates/session-progress.yamlto that path and stampcreated_at.workspace/taxpayer/profile.yamlif it exists. Otherwise prepare to create it fromtemplates/taxpayer-profile.yaml.
Items 3-4 are internal rules. Do not quote or summarize them to the user unless the user offers credentials, asks for login/submission help, or a specific uploaded item needs review.
Use session-progress.yaml to decide what to ask next. Never re-ask a question already in sections.intake.answered.
Workspace location
All workspace/... paths must resolve to one working folder that stays constant across every turn and every resumed session. On the first turn, set workspace_root to the absolute path of that folder and write it into both workspace/shared/session-progress.yaml and workspace/taxpayer/profile.yaml. On every later turn, read workspace_root back and resolve all workspace/... paths against it. Once set, never change it and never create a second workspace/ tree. See the Workspace root section of _shared/knowledge/methods/interactive-elicitation.md for the full contract.
Do not volunteer the folder path in the opening message. State the storage location only when the user asks where files are saved or when a later resume problem requires it. A later session relies entirely on finding profile.yaml and session-progress.yaml; if the host is pointed somewhere else, the resume guard cannot fire and intake will wrongly restart.
What this skill produces
Across one or more turns of conversation:
workspace/taxpayer/profile.yaml- incrementally filled as the user answersworkspace/shared/session-progress.yaml- updated every turnworkspace/shared/missing-info.md- items the user could not yet provideworkspace/shared/assumptions.md- confirmed assumptions, if any
Conversation flow
Turn 1 - open warmly, then ask the first screening batch
If workspace/taxpayer/profile.yaml does not exist, briefly explain what you'll do (prepare a local workpack), then ask up to four short screening questions in one message:
- Residency - Were you a Dutch resident for the full of 2025 (and, if relevant, 2026)? Did you move to or from the Netherlands at any point during the year? (A mid-year move usually means an M-aangifte, which v1 does not cover -- see
reference/unsupported-cases.md#1/#5.) - Taxpayer type - Are you filing as an individual? Do you have income from your own business, and if so what legal form (eenmanszaak / ZZP, or a VOF / maatschap / BV)? (An eenmanszaak / ZZP is supported; partnerships and BVs route to a specialist.)
- Living status - Is this for a living taxpayer?
- Workflow - What do you want help with: the annual 2025 return, or a 2026 voorlopige aanslag (request / change / review / stopzetten)?
Tell the user they can answer all at once or one at a time. Never ask for names or BSN. If the user volunteers a name, you may store it in person.display_name as a plain label for readability; it is never required and never verified.
If the user is unsure about question 4 (the voorlopige-aanslag terms are jargon most people do not use, and many confuse voorlopige aanslag with voorlopige teruggaaf or just say "I get/pay money monthly"): do NOT repeat the jargon list. Read reference/filing-paths.md and disambiguate by intent -- ask "Do you want to look back at what happened in 2025, or plan ahead for 2026?" -- then narrow the 2026 case using the request / change / review / stopzetten decision tree in that file.
Turn 2+ - record, then continue
After each user reply:
- Parse out everything the user answered. For each value, record it in
workspace/taxpayer/profile.yamlwithsource: user_chat, a short verbatimquote, andstated_at(today's date). - Append the answered
question_ids tosections.intake.answeredinsession-progress.yaml. - Update
sections.intake.statustoin_progress. - Decide the next thing to ask:
- If any of the four screening answers are still missing, ask only those.
- If the case is unsupported or terminal manual review (see below), say so clearly, close intake as terminal, and stop.
- If the workflow is identified, ask a short batch of follow-ups:
- Fiscal partner? Yes / no. If yes, do NOT collect partner BSN - only whether a partner exists.
- Winst uit onderneming + business-form screen (annual_2025 only): If the user reports income from their own business, record it in the
business:section. If the legal form is an eenmanszaak / ZZP, setbusiness.has_onderneming.value: true, recordbusiness.legal_form.value: eenmanszaakand (when stated)business.urencriterium_metandbusiness.starter_status, keepworkflow_candidate: annual_2025, and continue intake -- the Winst uit onderneming phase (2A) prepares it. If the form is a partnership (VOF / maatschap / CV), a BV / DGA-winst, an agrarische onderneming, or the taxpayer is a zeevarende, if the income looks like resultaat uit overige werkzaamheden (a freelancer who is not an ondernemer voor de inkomstenbelasting), or if the case involves staking/cessation, herinvesteringsreserve, or oudedagsreserve wind-down: recordrouting.complex_business_screening.value: manual_review, setmanual_review.required.value: true, record the trigger(s), setworkflow_candidate: annual_2025_entrepreneurs(the blocked candidate) ormanual_review, record it inrouting.blocked_profile_candidate, setintake_status: complete, setsections.intake.status: complete, mirror the terminal candidate intoactive_workflow, leaveactive_skillempty, and stop. Winst uit onderneming is annual-only; never route it into a provisional flow. - Box 2 existence + early complex Box 2 screen: Ask one explicit yes/no rather than waiting for the user to volunteer jargon: "Do you own 5% or more of a company (a BV / aanmerkelijk belang)?" Record the yes/no answer in
box2.has_aanmerkelijk_belangwith provenance. If yes -- or if the user mentions a BV, DGA role, aanmerkelijk belang, dividends, a share sale, an own BV loan, or a Box 2 estimate -- ask before the workflow-specific anchor: "Does the Box 2 situation involve a share sale or valuation dispute, emigration/immigration, restructuring, inheritance or gift, non-arm's-length pricing, or borrowing from your own BV?" If yes or unclear, recordrouting.complex_box2_screening.value: manual_review, setmanual_review.required.value: true, record the trigger(s), setworkflow_candidate: manual_review, setintake_status: complete, setsections.intake.status: complete, setactive_workflow: manual_review, leaveactive_skillempty, and stop.manual_reviewis terminal: do not call annual/provisional workflows and do not leave it as an unknown workflow candidate. - Workflow-specific anchor:
annual_2025-> "Do you already have any documents (jaaropgaaf, bankafschriften, WOZ, mortgage jaaroverzicht), or shall we collect amounts step by step in chat?"provisional_2026_request-> "Do you have a rough estimate of your 2026 income, or do you want me to ask category by category?"provisional_2026_change/review-> "Do you have your current voorlopige aanslag beschikking handy, or shall we reconstruct the baseline together?"provisional_2026_stopzetten-> "Are you currently RECEIVING a monthly refund (teruggaaf) or PAYING a monthly amount?"
Stopzetten routing consequence (apply at intake, where the answer is first captured): if the user is PAYING a monthly amount (not receiving a refund), do NOT set workflow_candidate: provisional_2026_stopzetten. Stopzetten only applies to a refund; stopping payments does not reduce the debt and risks a lump-sum bill at annual time. Explain this and set workflow_candidate: provisional_2026_change instead. Record the refund-vs-paying direction either way in profile.yaml → workflows.provisional_2026.stopzetten_direction (value receiving_refund or paying_monthly, with source/quote/stated_at provenance) so the downstream skill has it.
Household composition (before closing intake)
If the workflow is annual_2025 or any provisional_2026_* flow, also collect household composition. The annual workpack's credits screening (IACK, ouderenkorting, alleenstaande-ouderenkorting, jonggehandicaptenkorting) depends on these facts. Ask in a single batch of at most 3 questions, persisting each answer to profile.yaml -> person, partner, and household:
- Date of birth of the taxpayer (and of the fiscal partner if one exists). Persist to
person.date_of_birthandpartner.partner_date_of_birth. Deriveaow_age_in_tax_yearfrom the DOB and the tax year, store it withsource: assumption, addassumption_idon the profile field, and create the matching row inworkspace/shared/assumptions.md(for exampleA001: AOW-age status derived from DOB for tax-year credits screening) so the user can correct it if AOW age was reached mid-year. - Children at home on 31 December of the tax year: count and, for any child under 18, their date of birth (DOBs only -- never BSN). Persist to
household.children_at_home_countandhousehold.children. - Single-parent status: yes / no. Persist to
household.single_parent_status.
Short-circuit: if the user has already stated they have no fiscal partner and no children, you only need the taxpayer's date of birth (for AOW-age derivation). Skip the children-DOB and single-parent questions -- they are vacuously answered (children_at_home_count: 0, single_parent_status: false). Only ask the children/single-parent questions when a partner or any child has been mentioned.
Mark sections.intake.subsections.household_composition.status: complete in session-progress.yaml once these are answered. If the user defers, mark status: deferred and add the items to missing-info.md -- the annual workflow will re-prompt in Phase 1.7.
Closing the intake section
Mark sections.intake.status: complete only when:
- Residency, taxpayer type, living status, and workflow are all answered or recorded as
unsupported_reason. - Fiscal-partner status is recorded.
- The workflow-specific anchor question is answered.
- Household composition (DOB taxpayer + partner if applicable, children at home, single-parent status) is recorded for
annual_2025and provisional flows -- or each missing item is inmissing-info.mdand the corresponding subsection isdeferred. - For terminal routes (
manual_review,unsupported, or a blockedannual_2025_*profile candidate), the terminal reason is recorded and no annual/provisional workpack skill is selected.
Before closing intake, assert the resume contract holds. A resuming agent relies entirely on these files; if they are not populated it will wrongly restart intake:
workspace/shared/session-progress.yamlexists, is non-empty, and hasworkspace_rootset.sections.intake.statusiscompleteand every answeredquestion_idis insections.intake.answered.session-progress.yamlis self-describing: setactive_workflowto the chosenworkflow_candidate, setactive_skillto the skill that runs next (e.g.nl-tax-annual-returnornl-tax-provisional-assessment), and for a provisional flow setsections.provisional_2026.subflowtorequest/change/review/stopzetten. For terminal routes, setactive_workflowto the terminal candidate and leaveactive_skillempty.workflow_candidateinprofile.yamlremains the source of truth; these fields mirror it so the resume file is not stale.workspace/taxpayer/profile.yamlhasworkflow_candidateset,workspace_rootset, andintake_status: complete.updated_atis stamped on both files.
Once complete, write a one-paragraph summary back to the user and tell them which skill will run next:
annual_2025-> "Next: I'll guide you through evidence and the 2025 return one section at a time."provisional_2026_*-> "Next: I'll walk through the 2026 estimates category by category."
Do NOT auto-invoke the next skill. Wait for the user to continue.
Three paths for every input
For every fact you record, the user may take one of three paths:
- Upload a file to
uploads/orevidence/- hand off to thenl-tax-evidence-indexerskill. The corresponding subsection insession-progress.yamlbecomescompleteonce the file is indexed and the value extracted. - State the value in chat only - record it with
source: user_chat, a verbatimquote, andstated_at. Mark the corresponding subsection'sstatus: chat_only. This is an explicit choice, not a gap; do not nag for a file the user has declined to upload. - Defer ("I'll send it later") - record
source: unknown, mark the subsection'sstatus: deferred, add the item tomissing-info.md, and move on. The downstream workflow skill will re-prompt.
chat_only and complete both count as filled for the workpack generation gate. deferred must be resolved or explicitly accepted as missing before the gate opens.
Unsupported cases
Read reference/unsupported-cases.md. If you detect an unsupported case (part-year resident, a complex business form, deceased taxpayer, M-biljet required, etc.) — note that a standard eenmanszaak / ZZP is supported and is NOT an unsupported case:
- Tell the user clearly and kindly that v1 does not cover their case.
- Set the most specific terminal or blocked profile candidate in
workflow_candidatewhen one matches the case:
annual_2025_entrepreneursfor complex business-profit cases only — part
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: cyanxxy
- Source: cyanxxy/nl-tax-agent-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.