Personal and household finance for the Oshun monorepo: a deep, typed library of financial domain logic (account modelling, budgeting, tax, investments, retirement, estate, behavioral finance) sitting on a Drizzle/PostgreSQL data layer, fronted by a set of service applications. This page describes the real code under
libs/lakshmi/*andapps/lakshmi/*, and is honest about which parts are fully implemented versus which are runtime skeletons not yet wired to the domain logic.
What Lakshmi Is#
Lakshmi is the bounded context for personal and household financial intelligence. Its purpose is to give a user a single, consent-driven view of every account they hold — checking, savings, credit cards, loans, brokerage and retirement accounts, crypto wallets, real estate, insurance policies, business equity — and to turn that aggregated picture into planning: budgets, debt payoff, tax optimization, retirement projection, estate readiness, and behavioral nudges.
Where Neith is the lowest layer of the stack, Lakshmi is a product domain near the top: it consumes shared infrastructure and adjacent domains' facts (Aje for on-chain assets, Cybele for property facts, Gaia for climate/energy inputs) and owns one thing exclusively — the personal-finance decision. It is the source of truth for what a user's net worth is, how their budget is tracking, and whether their retirement is on course. It is named after the Hindu goddess of wealth and prosperity.
The design principle that shapes every layer is consent-first: no financial data enters the system without explicit user consent, every connection is revocable, and every row is scoped to its owner at the database level.
Implementation Status — Read This First#
Earlier drafts of this page called Lakshmi a "fully-implemented platform" where "every feature exists in source." That is half-true and worth stating precisely, because the split matters for anyone working here:
- The domain libraries are real and deep. The 24 packages under
libs/lakshmi/*contain genuine, domain-specific financial logic — calculation engines (amortization, time-value-of-money, Monte Carlo, tax brackets, risk metrics, Social Security), a 32-variant account model, TF-IDF transaction categorization, portfolio performance math (TWR/MWR/XIRR), tax-loss harvesting, and more. These are pure input→output modules with their own types and tests. - The data layer is real.
@lakshmi/dbdeclares a full Drizzle schema, RLS policy generation, TimescaleDB hypertable plans, a pgvector similarity index, connection pooling, a migration registry, and seed data. - The service runtime is partly a skeleton. The six
apps/lakshmi/*services provide real infrastructure — a Hono gateway with JWT/OAuth/RBAC middleware and OpenAPI generation, a BullMQ queue/DLQ system, a cron scheduler, typed Kafka event schemas. But the HTTP data plane is not yet wired to the domain libraries: the gateway's route handlers return placeholder empty payloads ({ accounts: [] },taxProfile: null) with comments like "Domain service call will be implemented in@lakshmi/accountslibrary." The sync-engine and ai-agents services currently serve only health checks; the latter says so explicitly in its source.
So Lakshmi today is a strong domain core and data layer with a deliberately
fail-honest service shell around it. The libraries can be imported and used
directly; the end-to-end HTTP/sync/AI pipelines are scaffolding awaiting wiring.
Items genuinely deferred are labelled (planned) below.
Workspace Shape#
The domain is 24 TypeScript libraries under libs/lakshmi/* and 6 service
applications under apps/lakshmi/*.
apps/lakshmi/
api-gateway/ Hono HTTP/WS gateway: auth, RBAC, OAuth scopes, OpenAPI, Kafka bus (:4200)
sync-engine/ health/ready surface; open-banking polling (planned wiring) (:4201)
ai-agents/ health/ready surface; financial agents (planned wiring) (:4202)
worker/ BullMQ background worker: 5 queues + DLQs
scheduler/ BullMQ repeatable-job scheduler (:3804)
browser-extension/ MV3 extension: shopping-context capture (popup/content/service-worker)
libs/lakshmi/
core/ db/ ← foundation
accounts/ integrations/ ← integration
transactions/ budgeting/ behavioral/ ← intelligence
investments/ tax/ debt/ credit/ retirement/
insurance/ estate/ real-estate/ crypto/
income/ goals/ household/ business/ ai-engine/
security/ ← security
reporting/ alerts/ ← experience
Note: there is no libs/contracts/src/lakshmi or
libs/openapi/src/specs/lakshmi package in source. Cross-domain contracts under
@contracts/lakshmi are (planned); the OpenAPI document is generated at
runtime inside the gateway (apps/lakshmi/api-gateway/src/public-rest-api.ts),
not published as a shared spec package.
Layered Architecture#
The libraries are organized into five dependency layers. Higher layers depend on lower ones; the reverse is never allowed, which keeps core primitives stable while the experience layer evolves.
- Core —
@lakshmi/core(money, account, user/household primitives, the calculation engine) and@lakshmi/db(the persistence layer). Foundation for everything above. - Integration —
@lakshmi/accounts(aggregation-provider adapters, sync, statement import, account management) and@lakshmi/integrations(open-banking, payroll, credit-bureau, crypto, real-estate, document gateways). Normalizes raw provider data into domain objects. - Intelligence — the bulk of the domain:
@lakshmi/transactions,@lakshmi/budgeting,@lakshmi/behavioral,@lakshmi/investments,@lakshmi/tax,@lakshmi/debt,@lakshmi/credit,@lakshmi/retirement,@lakshmi/insurance,@lakshmi/estate,@lakshmi/real-estate,@lakshmi/crypto,@lakshmi/income,@lakshmi/goals,@lakshmi/household,@lakshmi/business, and@lakshmi/ai-engine. Derives meaning and recommendations from ingested data. - Security —
@lakshmi/security(encryption, key derivation, RBAC, MFA, GDPR/CCPA/SOC2 controls, privacy-preserving primitives). Enforces privacy across layers, complementing the database's row-level security. - Experience —
@lakshmi/reporting(statements, exports, shareable links) and@lakshmi/alerts(threshold notifications), surfaced through theapps/lakshmi/*services.
The Core Domain Model (@lakshmi/core)#
@lakshmi/core is the foundation every other library imports. Its public
surface (libs/lakshmi/core/src/index.ts) re-exports caching, Prometheus
metrics, financial primitives, domain types, and the calculation engine. The
specifications page enumerates the full type system; the
load-bearing pieces are:
- Branded ID types (
Brand<T, B>intypes/account.tsandtypes/user.ts) —UserId,HouseholdId,AdvisorId,AccountId,ConnectionId,InstitutionId,ManualAssetId. These are compile-time unique so anAccountIdcannot be passed where aUserIdis expected. FinancialAccount(libs/lakshmi/core/src/types/account.ts) — the central type: a discriminated union over atypefield with 32 account variants across eightAccountCategorygroups (depository,credit,loan,investment,crypto,real_estate,business,manual). Each carries anAccountSign(asset/liability) that drives its net-worth contribution. Using a discriminated union forces every code path to handle every variant.Money(libs/lakshmi/core/src/primitives/money.ts) — an immutable value object holding integer minor units plus an ISO 4217 currency. Arithmetic preserves currency and rejects cross-currency operations;fromDecimal,divide,allocate, andsplituse banker's rounding (round-half-to-even) and conserve remainder pennies. Every monetary field in the domain is integer cents to avoid floating-point drift.- The calculation engine (
libs/lakshmi/core/src/calculations/) — a deterministic suite kept separate from the domain types so it can be tested against known answers:amortization(fixed/ARM/interest-only/balloon, per-payment schedules, extra-payment modelling),tvm(PV/FV/NPV/IRR/XIRR),monte-carlo(retirement and goal simulation),tax-brackets(federal + 50-state, 2024),risk-metrics(Sharpe/Sortino/VaR/CVaR/beta/alpha), andsocial-security(PIA/COLA/claiming adjustments).
Transactions and goals are deliberately not core entities — they are owned
by @lakshmi/transactions and @lakshmi/goals and persisted by @lakshmi/db.
Persistence and the Data Layer (@lakshmi/db)#
@lakshmi/db (libs/lakshmi/db/src/index.ts) is fully implemented and is the
single owner of Lakshmi's PostgreSQL schema.
- Namespaces and tables.
src/schema/namespaces.tsdeclares 19 PostgreSQL schema namespaces (accounts,transactions,budgets,investments,tax,debt,credit,retirement,insurance,estate,real_estate,crypto,income,goals,behavioral,household,business,alerts,audit), mirrored byLAKSHMI_SCHEMA_NAMESin the migration registry. Acrosssrc/schema/*.tsthere are roughly 80 Drizzle tables (82…Schema.table(...)declarations), mapped to their namespaces byLAKSHMI_TABLE_SCHEMA_BY_NAME. (Earlier drafts said "20 namespaces / 78 tables"; the source has 19 and ~80.) - Row-level security.
src/migrations.tsgenerates real RLS SQL. Helpers likebuildUserScopedRlsPolicy,buildHouseholdSharedRlsPolicy,buildHouseholdOwnerRlsPolicy, andbuildParentScopedRlsPolicyemitENABLE ROW LEVEL SECURITYplusCREATE POLICYstatements predicated oncurrent_setting('app.current_user_id', true)::uuid. A query physically cannot return another user's rows even if an application check is bypassed — the database enforces the boundary. - Time-series.
TIMESCALE_HYPERTABLE_CONFIGS+applyTimescaleHypertablesconvert the time-series tables (balance/net-worth/price/benchmark/credit-score history) into TimescaleDB hypertables viacreate_hypertable(...), and the routine skips gracefully if TimescaleDB is not installed rather than failing the migration. - Vector search. The same migration builds a pgvector IVFFlat index on
transaction embeddings for similarity-based categorization. (The gateway's
/v1/transactions/searchendpoint anticipates this but is not yet wired.) - Connection pooling.
src/connection.tsexposes three purpose-builtpgpools — a primary pool, a high-throughput transaction-ingest pool (short statement timeout, larger max), and an analytics pool (120 s statement timeout for Monte Carlo / report queries) — each wrapped by Drizzle. - Seed and reference data.
src/seed-data/ships 2024 federal/state tax brackets, IRS contribution limits, RMD uniform lifetime table, Social Security benefit tables, market benchmarks, credit-score factor weightings, the Plaid transaction-category taxonomy, and insurance product templates.
Intelligence: Where Data Becomes Insight#
The intelligence libraries are the heart of the domain. Each owns one reasoning
area, shares @lakshmi/core primitives, and is otherwise independent so it can
be tested and evolved alone. A representative example shows the depth and the
honesty caveat that applies to a few modules:
Transaction categorization
(libs/lakshmi/transactions/src/categorization/engine.ts) is a real algorithm:
text normalization → tokenization → TF-IDF scoring against a per-category
keyword centroid → cosine similarity → confidence tiering (HIGH ≥ 0.85,
MEDIUM 0.70–0.84, LOW 0.50–0.69, VERY_LOW < 0.50), with low-confidence
results routed to a ManualReviewQueue and a PersonalizationEngine that
learns from user corrections. (The module's docstring quotes accuracy
percentages and a future pgvector/Qdrant ANN path; treat those as aspirational —
the shipped logic is the TF-IDF cosine pipeline, and the README's "98%+ accuracy
/ 20,000+ institutions" are marketing claims, not measured metrics.)
The other intelligence modules follow the same pattern of concrete domain math:
@lakshmi/budgeting has distinct ZeroBudgetEngine/CategoryBudgetEngine/
CashFlowBudgetEngine plus velocity tracking and forecasting;
@lakshmi/investments computes TWR/MWR/XIRR, VaR/CVaR, factor exposure, and
tax-aware rebalancing; @lakshmi/tax does wash-sale-aware tax-loss harvesting
and Roth-ladder planning; @lakshmi/retirement runs Monte Carlo projections and
Social Security optimization; @lakshmi/estate tracks beneficiaries, an
encrypted document vault, and probate-avoidance analysis. The specifications
page lists the engine classes per module.
@lakshmi/ai-engine is intended to sit above these modules and provide
reasoning, recommendation, and explanation. The library exists; the service
that would orchestrate it (apps/lakshmi/ai-agents) is a health-check skeleton
today (see below).
Service Runtime — Honest State#
API Gateway (apps/lakshmi/api-gateway, port 4200)#
src/app.ts builds a real Hono application. Implemented: a global
middleware stack (request logger, CORS with an allow-list, secure headers),
/health and /ready probes (the latter pings Redis and checks Kafka), runtime
OpenAPI generation (/openapi.json, /v1/meta,
/.well-known/oauth-authorization-server), and a v1 router protected by
authenticate() (JWT verification via jose, claims in
src/middleware/auth.ts), rateLimitMiddleware (Redis-backed), per-route OAuth
scope enforcement (requireOAuthScopes, driven by the endpoint catalog), and
per-route RBAC (requirePermissions, driven by
LAKSHMI_ROUTE_PERMISSION_POLICIES). The JWT model supports
advisor-acting-on-behalf-of-client and household-role claims.
Not implemented: the route handlers themselves. GET /v1/accounts returns
{ accounts: [] }; /v1/tax/profile returns taxProfile: null; almost every
handler returns an empty array or null with a comment that the domain-service
call is still to be added. The gateway is a complete, secured HTTP front door
that does not yet call the domain libraries or the database.
Sync Engine (apps/lakshmi/sync-engine, port 4201)#
The docstring describes open-banking polling across
Plaid/Yodlee/MX/Finicity/Tink and Kafka publication, but src/index.ts
currently implements only a node:http server answering /health and /ready
(reporting the configured provider list, sync interval, and concurrency from
env). The provider adapters live in @lakshmi/accounts/providers; the service
does not yet invoke them. (Polling loop: planned.)
AI Agents (apps/lakshmi/ai-agents, port 4202)#
src/index.ts is explicit and fail-honest: "The financial agents themselves …
are NOT yet implemented in this service — this process currently serves health
checks only." Its health payload reports only the model providers whose
credentials are actually present in the environment
(resolveConfiguredModelProviders checks
OPENAI_API_KEY/ANTHROPIC_API_KEY/Google keys) rather than asserting
connections that may not exist. (Agent orchestration: planned.)
Worker and Scheduler#
apps/lakshmi/worker/src/queues.ts is fully implemented BullMQ infrastructure:
five priority queues (account-sync, transaction-categorize,
ai-recommendation, report-generation, data-export), per-queue
retry/backoff policies, typed job payloads, enqueue helpers with deduplicated
job IDs, a typed createWorker factory with structured logging, and an
attachDlqForwarder that moves permanently-failed jobs to per-queue dead-letter
queues. The job processors (the functions that would call domain logic) are
injected by the caller and are not yet supplied.
apps/lakshmi/scheduler/src/schedules.ts defines the real recurring tasks
(buildRecurringTasks): a 15-minute due-connection sync sweep, a 5-minute
uncategorized-transaction sweep, a daily AI-recommendation cron (0 3 * * *), a
daily report-rollup cron, and an hourly expired-export maintenance cron — each
enqueued into the corresponding worker queue with a stable jobId.
Browser Extension (apps/lakshmi/browser-extension)#
A Manifest V3 extension (service worker, content script, popup) that captures
shopping context (src/shared/shopping-context.js) to support behavioral/spend
insight at the point of purchase. It is plain JS with its own build and
validation scripts and Playwright/vitest tests.
Event Bus#
apps/lakshmi/api-gateway/src/events.ts defines versioned Zod schemas for all
domain events (account-sync, new/categorized transactions, alerts, rebalance,
tax harvest, goal milestone, AI recommendation) with explicit Kafka topic
routing and a correlation ID propagated from the originating request — the
contract by which the gateway, sync engine, worker, and downstream consumers
would decouple.
Component and Data Flow#
The diagram shows the intended runtime topology. Solid arrows are wired today; dashed arrows are the planned handler/processor wiring that is not yet in source.
Invariants, Failure Modes, and Extension Points#
Invariants the codebase enforces.
- Money is integer minor units. Every monetary value is cents; arithmetic goes
through
Money(banker's rounding, penny-conserving allocation, currency-safe). - IDs are branded. Cross-entity reference errors are caught at compile time.
- Rows are user-scoped at the database. RLS predicated on
app.current_user_idis the last line of defence regardless of application bugs; household-shared, household-owner, and parent-scoped policies layer on top. - Accounts are exhaustive. The 32-variant discriminated union forces every consumer to handle every account type — adding a variant surfaces every place that must change.
- Consent-first and revocable. An
AccountConnectiononly refreshes whenactive; revocation moves it torevoked.
Failure modes the code already handles honestly.
- Missing credentials fail loud, not fake.
@lakshmi/dbthrows ifLAKSHMI_DATABASE_URLis unset; ai-agents reports only configured model providers; readiness returns503when Redis or Kafka is down. - Degraded infrastructure degrades gracefully. TimescaleDB hypertable creation is skipped (with a warning) when the extension is absent.
- Background jobs have a dead-letter path. Jobs that exhaust retries are forwarded to a DLQ with the original payload, failure reason, and attempt count.
Extension points. Add an account type by extending the FinancialAccount
union and its helper switches in core/src/types/account.ts. Add an aggregation
provider via the AggregationProvider enum and a @lakshmi/accounts/providers
adapter. Add a background job by defining its payload + queue in
worker/src/queues.ts and a sweep in scheduler/src/schedules.ts. Add an HTTP
endpoint by extending the gateway route catalog (which auto-derives OAuth-scope
enforcement and the OpenAPI document). The most valuable open work is wiring the
existing gateway handlers and worker processors to the existing domain
libraries.
Cross-Domain Boundaries#
Lakshmi's boundaries are drawn around ownership of the financial decision, not raw data. Each adjacent domain owns its own source of truth and exposes it through a defined contract:
- Aje owns blockchain/Web3 infrastructure;
@lakshmi/cryptoconsumes on-chain asset data from Aje rather than re-implementing chain access, and owns how those assets appear in net worth and tax. - Cybele owns property records, construction costs, and AVM data;
@lakshmi/real-estateconsumes those facts but owns affordability, buy-vs-rent, equity, and rental economics. - Maat owns enterprise/organizational finance; it may consume aggregate, consented business intelligence from Lakshmi but shares no database or model.
- Themis supplies governance policy and immutable audit records used for legal-transfer events (the V2 estate-vault surface).
- Gaia (Phase 175) supplies renewable-energy-potential and climate-risk products that Lakshmi consumes for household capacity planning, insurance context, property-risk, and financial-scenario modelling.
Two V2 adapter surfaces — @v2/lakshmi-responsible-play-spend-insight
(composing behavioral/budgeting/transactions for responsible-play store
confirmation) and @v2/per-account-vault-estate-bridge (bridging estate data to
@oshun/identity and @themis/transparency) — live in the V2 services
tree, not under libs/lakshmi. Both are off-rollback and must never feed
deterministic match simulation or competitive outcomes. See
features.md for their contracts.
Verification Expectations#
Every financial calculation, categorization, aggregation, and alert path is expected to carry deterministic tests (asserting computed values against known-correct answers), plus privacy, consent, audit, and contract tests. Integration tests use sandbox fixtures and never require live aggregation, bureau, or model-provider credentials in CI — which is why the service layer's external integrations are environment-gated and fail loud when unconfigured rather than fabricating connections. The honest gap to close is the data-plane wiring between the services and the already-built domain libraries.