A layered architecture with clear boundaries: the rules engine is deterministic and pure, the surface layer adapts to each mode.
OpenTax is three layers. The bottom two are shared; only the top layer differs between standalone and plugin mode.
Layer 3 — Surface (mode-specific) ┌──────────────────────┐ ┌──────────────────────┐ │ Standalone UI │ │ OpenClaw Plugin │ │ │ │ │ │ React + Router │ │ Express server │ │ Zustand store │ │ REST API + SSE │ │ OCR (Tesseract.js) │ │ 16 agent tool defs │ │ PDF gen (pdf-lib) │ │ SQLite persistence │ │ IndexedDB storage │ │ PDF gen (pdf-lib) │ └───────────┬──────────┘ └───────────┬──────────┘ │ │ ▼ ▼ Layer 2 — Service API (shared) ┌─────────────────────────────────────────────────┐ │ TaxService │ │ │ │ createReturn() addW2() compute() │ │ add1099() setCredits() traceLine() │ │ getSummary() generatePdf() │ └────────────────────────┬────────────────────────┘ │ ▼ Layer 1 — Rules Engine (shared, deterministic) ┌─────────────────────────────────────────────────┐ │ Rules Engine │ │ │ │ IRS rules (Rev. Proc. 2024-40, Pub 501, ...) │ │ Tax brackets · Standard deduction │ │ Credits · Deductions · AMT · State (CA) │ │ Trace tree generation · IRS citations │ │ Deterministic: same input → same output │ └─────────────────────────────────────────────────┘
The engine is a pure-function computation graph. Given identical inputs, it always produces identical outputs. No side effects, no network calls, no randomness.
compute() function
src/rules/2025/ ├── constants.ts # Brackets, thresholds (Rev. Proc. 2024-40) ├── form1040.ts # Main 1040 computation ├── taxComputation.ts # Tax-on-income calculation ├── scheduleA.ts # Itemized deductions ├── scheduleD.ts # Capital gains ├── form8949.ts # Capital asset sales ├── form8889.ts # HSA deductions ├── amt.ts # Alternative Minimum Tax ├── childTaxCredit.ts # CTC / ACTC ├── earnedIncomeCredit.ts # EITC ├── educationCredit.ts # AOTC + LLC ├── saversCredit.ts # Retirement savings credit ├── hsaDeduction.ts # HSA above-the-line deduction └── ca/ ├── constants.ts # California thresholds └── form540.ts # CA 540 computation
Every computed value in the engine produces a trace node. The trace tree is the foundation of OpenTax's explainability.
Value extracted from an uploaded document (W-2 Box 1, 1099-INT amount).
Derived by the rules engine from other values (total tax, taxable income).
Manually entered by the user during the interview (filing status, dependents).
{
"nodeId": "form1040.line24",
"label": "Total tax",
"amount": 1173400, // cents (integer)
"irsCitation": "Form 1040, Line 24",
"sourceType": "computed", // "document" | "computed" | "user-entry"
"confidence": 1.0, // 0.0–1.0 (OCR = lower)
"inputs": [
{ "nodeId": "taxComputation.taxOnIncome", ... },
{ "nodeId": "form1040.line23", ... }
]
}
The data flow is fundamentally different in each mode. The engine itself is identical—only the I/O boundary changes.
TaxService.compute() which runs the rules engine in the main thread.
Network calls: Zero. All data stays in the browser.
/.well-known/openclaw.json.
TaxService.compute() in Node.js. Progress streams via SSE.
Network calls: Agent ↔ plugin server (localhost or private network).
Clear separation of concerns keeps the codebase maintainable and each layer independently testable.
The rules engine is pure computation. It takes a canonical tax model as input and produces computed forms + trace tree as output. It never touches I/O, storage, or network.
TaxService wraps the engine with return lifecycle management (create, update, compute, trace). Both modes consume TaxService; neither calls the engine directly.
In standalone mode, the React UI imports TaxService directly as an ES module. In plugin mode, the Express server wraps TaxService behind REST endpoints and SSE streams.
The surface layer handles I/O concerns: storage (IndexedDB vs. SQLite), document handling (client-side OCR vs. agent-provided data), and output (React rendering vs. JSON responses).
The plugin boundary is the OpenClaw protocol. The plugin exposes tools and accepts structured JSON input. It makes no assumptions about the agent's implementation, LLM provider, or conversation history. Any OpenClaw-compatible agent can integrate.
Tax data is sensitive. The architecture is designed to minimize exposure.
The layered architecture enables focused testing at each boundary.
| Layer | Test type | What's verified |
|---|---|---|
| Rules engine | Unit tests (1,700+) | Every form computation against IRS-published expected values |
| TaxService | Integration tests | Return lifecycle: create → populate → compute → trace |
| Plugin API | HTTP tests | REST endpoints return correct status codes, shapes, and data |
| UI | Component tests (129+) | Interview flow, responsiveness, accessibility, touch targets |
Choose your path and start building with the OpenTax engine.