← Vesper homeGo to dashboard →

How Vesper works

Last updated 2026-06-02. The sections below describe what the current deployed product does; when product decisions change, this page changes. The Changelog at the bottom is the append-only record of what shipped, when, and in which deploy.

Who Vesper is for: the muni thesis in plain terms

Municipal bonds used to be perceived as a product for top-bracket investors only, useful if you were paying 35–37% federal tax and irrelevant otherwise. That perception is out of date. With current muni yields and the rate environment, the break-even is meaningfully lower than most advisors and clients think.

Anyone in the 22% federal bracket or higher typically comes out ahead on an after-tax basis with a muni ladder versus comparable taxable bonds. The math:

Client in the 22% federal bracket.
Muni ladder yields 3.50% YTW (reasonable current muni rate).
TEY = 3.50% / (1 − 0.22) = 4.49%

A comparable-duration Treasury at 4.00%? Muni wins by 49bps after tax.

Move up to the 24% bracket:
TEY = 3.50% / (1 − 0.24) = 4.61% → muni wins by 61bps.

Add a 6% state tax (in-state muni) for the 24% federal client:
TEY = 3.50% / (1 − (0.24 + 0.06 − 0.24 × 0.06)) = 4.92%
Muni wins by 92bps, nearly a full percentage point of take-home yield.

So when you're pitching Vesper to clients, "it's not just for the Rockefellers" is a fair framing. Anyone with enough of an income to hit the 22%+ bracket and enough taxable-account savings to warrant a ladder is a plausible candidate. That's a much larger total addressable market than the traditional <37%-bracket narrative implies.

What Vesper does

Vesper automates municipal bond ladder construction for registered investment advisors (RIAs). A muni ladder is a portfolio of municipal bonds with staggered maturities (typically one bond per year out to some horizon) so the client gets a predictable stream of interest payments and principal repayments. Muni interest is exempt from federal income tax (and often state tax too), which is why high-bracket clients use them.

Building one manually: an advisor screens thousands of available bonds against the client's tax bracket, state, risk tolerance, and horizon, picks one bond per rung, checks for concentration in individual issuers or sectors, and writes up the rationale. It's typically 3–6 weeks of work.

Vesper compresses that to minutes. The advisor enters the client's profile once, sets a few constraints for each ladder, and a constraint-based solver (Google OR-Tools CP-SAT) picks bonds that maximize after-tax yield while respecting every constraint.

The math that matters: Tax-Equivalent Yield (TEY)

Munis pay lower headline yields than taxable bonds, but the interest is tax-free. TEY is the apples-to-apples comparison: the yield a taxable bond would have to pay to deliver the same after-tax income.

TEY = muni_yield / (1 − marginal_tax_rate)

Example. 37% federal bracket, 3.00% muni:
TEY = 0.03 / (1 − 0.37) = 4.76%

A 4.50% corporate bond pays less take-home than this 3% muni.

For a client who lives in the state a muni is issued from, interest is also exempt from state tax. Vesper combines federal + state rates (with the federal deduction for state taxes approximated):

TEY_in_state = muni_yield / (1 − (fed + state − fed × state))

Every bond in a ladder's result page shows both its YTW (pre-tax) and TEY (what that YTW translates to after the client's specific taxes).

Marginal vs. effective rate

The headline TEY uses the client's marginal rate (the bracket on their next dollar). That is the standard comparison, but it is also the most generous one: marginal is higher than the client's effective (average) rate, so it produces the largest break-even. If you enter an optional effective federal rate on the client, the break-even banner additionally shows the comparison at that lower rate and leads with it, because it is the more conservative number to put in front of a client. The marginal figure is shown as the upper bound.

This is display-only. The optimizer selects bonds by maximizing pre-tax yield (YTW), so neither rate changes which bonds are chosen; they only change the after-tax comparison shown. The AMT filter is a bracket concept and stays keyed to the marginal federal rate. The 3.8% NIIT is reflected on the break-even: tax-exempt muni interest is excluded from it, but taxable interest is not, so for a NIIT-subject client the break-even shows how much higher a taxable bond's yield would have to be to keep up.

Adding a client

A client record is the anchor for every ladder you'll build for that person. The tax profile is the load-bearing part; everything else is metadata.

Name (required)
The client's full name. Appears on ladder titles and future PDF reports.
Email (optional)
Stored for future use (report delivery, notifications). Nothing reads it today.
State (required)
The US state the client lives in. Drives two things: whether a muni qualifies for double tax exemption (residents of the issuing state avoid state income tax on that muni's interest), and the optimizer's in-state preference constraint when enabled.
Filing status
IRS filing category (Single / Married Joint / Married Separate / Head of Household). Stored for future use; will drive the 3.8% Net Investment Income Tax and AMT phase-out thresholds in later iterations. Not used by today's optimizer math.
Federal tax rate (%) (required)
The client's marginal federal bracket: the rate on their next dollar earned, not their average rate. Muni ladders typically benefit anyone at 22% and above on an after-tax basis (see "Who Vesper is for" at the top of this page for the break-even math); the seven 2024/2025 brackets are 10%, 12%, 22%, 24%, 32%, 35%, 37%.

Separately, rates ≥32% trigger the AMT filter in the optimizer: AMT-subject (private-activity) munis are excluded because clients at 32%+ are materially more likely to be subject to the Alternative Minimum Tax, where that interest becomes taxable. Nothing about being below 32% means munis don't work; it just means the AMT filter isn't applied.
State tax rate (%) (required)
The client's marginal state income tax rate. 0 for TX/FL/WA/NV/SD/TN/AK (no state income tax). Up to 13.3% for California top bracket. Combined with the federal rate to compute in-state TEY.
Investment amount ($) (optional)
Total dollars the client wants laddered. Optional here; defaults into the ladder form but can be overridden per ladder. When present, drives the total investment hard constraint and the concentration caps (20% per issuer → max dollars per issuer = 0.20 × investment amount).
Risk tolerance
Permanent client trait. Sets the default ladder risk profilewhen this client is selected on the ladder form. Three tiers (all investment grade):
  • Conservative: AA min rating, 5-year horizon, 5 years call protection, 20% max per issuer, 30% max per sector. Capital preservation; typical for retirees drawing income.
  • Moderate: A min rating, 10-year horizon, 3 years call protection, 20% per issuer, 30% per sector. Balanced; typical RIA default.
  • Aggressive: BBB min rating (lowest tier of investment grade), 20-year horizon, 0 years call protection, 20% per issuer, 30% per sector. Yield-seeking but still IG. Appropriate for high-bracket accumulators comfortable at the edge of investment grade.

The ladder form itself has a fourth option, High Yield, that isn't a client tier because it's meant to be an explicit per-ladder choice for satellite allocations, not a default posture. See the ladder section below.

The advisor can override any default on a per-ladder basis.
Notes
Free-form advisor notes. Never read by the system; reference only.

The client workspace

Client list at /dashboard/clients shows every client you've added: Name (clickable), State, Fed/State tax rates, Investment amount, Risk tolerance, Email. Row hover highlights for scanability. Adding a new client uses the form at the top of the same page.

Client detail page at /dashboard/clients/[id]. Click any client name to land here. Four cards show everything about that client: Tax profile (federal, state, combined-effective rate, filing status, AMT filter status), Investment profile (default amount, risk tolerance), Contact (email, phone), Notes. Below the cards is that client's ladder history: every ladder ever built for them with status, total invested, avg YTW / TEY, and links into each result page. A prominent "Build ladder for [First Name]" CTA pre-selects this client on the new-ladder form.

Building a ladder

The form is split into two numbered boxes to keep the workflow unambiguous:

Defaults come from the client's risk tolerance but every field is overridable.

Client
The client this ladder is being built for. Their federal + state tax rates, state, and risk tolerance are pulled from the client record.
Risk profile
A coordinated preset that prefills the rating floor, horizon, call protection, and concentration caps in one choice. Defaults from the client's tier but can be switched. Four options:
  • Conservative / Moderate / Aggressive: same as the three client tiers described above, all investment grade.
  • High Yield: BB+ minimum rating (crosses below investment grade into the high-yield muni space), 15-year horizon, 0 years call protection, and tighter concentration caps: 10% per issuer (vs. 20% default) and 20% per sector (vs. 30% default). The tightened concentration compensates for the higher default risk on any individual HY bond by pushing the solver toward more issuers and more sectors. Appropriate for satellite yield allocations, not meant to be a whole-portfolio posture.
Investment amount ($)
Total dollars for this ladder. Prefills from the client's stored amount. Must be high enough that each rung can meet bond minimum denominations (typically $5,000 per bond).
Time horizon (years)
Number of rungs. One (or more) bond matures each year out to this horizon. A 10-year ladder spans maturities across years 1–10. Shorter horizons mean less duration risk but lower yield.
Minimum rating
Credit quality floor. Bonds below this threshold are dropped before the solver runs. AAA = highest. BBB- is the lowest investment grade (BB+ and below is non-investment-grade / "high yield" / "junk"). The dropdown includes BB+ and BB for advisors who explicitly want HY exposure; picking either is usually done via the High Yield risk profile above, which also tightens concentration.
Max per issuer (%)
Diversification cap. No single issuer (e.g., State of California, a specific school district) can represent more than this fraction of the ladder's dollars. 20% is a common default; limits default risk if one issuer downgrades or defaults.
Max per sector (%)
Per-sector cap. Prevents concentration in one industry. The sector enum: General Obligation, Revenue, Water/Sewer, Transportation, Education, Healthcare, Housing, Power, Tax Increment, Other.
Min call protection (years)
Callable bonds can be redeemed early by the issuer (usually when interest rates drop, which hurts the investor). Call protection is the minimum years before the issuer can call. Higher = safer but fewer candidate bonds qualify.
Rung tolerance (years)
Maturity-drift tolerance per rung. 0 = strict; a bond must mature in the exact rung year to fill it. 1 = ±1 year accepted (a 2028 bond can fill the 2027 or 2029 rung). Critical when bond inventory is sparse, especially for high-yield munis where a specific state × sector × year combo may have very few available bonds. Each selected bond still fills exactly one rung; tolerance just widens the candidate pool per rung. Max ±3 years.
In-state minimum (%)
Minimum fraction of the ladder's dollars that must be in bonds issued in the client's state. Only applied when "Prefer in-state issuers" is checked. 100% = pure in-state. 0% = no minimum (diversify across states). Typical values: 60% (balanced), 80–100% (pure-local shops), 20–40% (diversifiers).
Include callable bonds with protection
If checked, callable bonds meeting the min-call-protection above are allowed in the ladder. Unchecked = non-callable only. Most ladders include callables because it widens the candidate pool dramatically.
Prefer in-state issuers
Enables the in-state minimum constraint. When checked, the optimizer must put at least the in-state minimum percent in bonds from the client's state.
Equal weight per rung
When checked, each maturity year gets roughly the same dollar allocation (within ±20% of the target). Produces a classic flat-rung ladder. Unchecked allows the optimizer to concentrate dollars in higher-yielding rungs.

Handling thin HY inventory: the three-part answer

High-yield muni inventory is famously thin. For a given state, in a given sector, in a given maturity year, there are often only a handful of bonds available, sometimes zero. Any ladder tool that can't handle this fails constantly on the HY use case. Vesper handles it with three tools that compose together:

  1. The High Yield risk profile doesn't just drop the rating floor to BB+. It also tightens per-issuer concentration to 10% and per-sector to 20%, forcing the solver toward more issuers and more sectors. This compensates for the higher default risk on any individual HY pick.
  2. Rung tolerance (0-3 years) lets a bond maturing in, say, 2028 fill the 2027 or 2029 rung when no 2028 bond is available at the required rating. Ladder shape preserved (one bond per rung, no double-counting) but the candidate pool per rung is much wider. For the HY use case, tolerance of 1 or 2 is typical.
  3. Partial ladders with gap reporting are the fallback. When some rung still has zero candidates even after tolerance and profile relaxation, the solver fills the rungs it can and returns the gaps explicitly. The result page shows which rungs are unfilled and offers concrete next steps (widen tolerance further, lower rating, reduce in-state minimum, etc.) instead of a blanket "infeasible" error.

In practice: if you're building a 10-year California HY ladder and three rungs come up short, you'll see a 7-rung ladder with an amber banner listing the unfilled years and the relaxations that would likely close the gaps. That's a useful tool even when the universe is sparse, instead of a binary fail.

How the optimizer actually picks bonds

Under the hood: Google OR-Tools CP-SAT, a constraint-programming solver that finds the optimal combination of decision variables subject to every constraint you give it. For Vesper the variables are (1) which bonds to select and (2) how much par to allocate to each.

1. Hard filters (prefilter)

Before the solver even runs, the universe is filtered. Bonds are dropped if they fail any of:

2. Scoring → top 200 candidates

Surviving bonds are scored 0–100 and the top 200 move on to the solver. The weights are defined in constants.py and shown on every ladder's result page:

3. Solver constraints

4. Objective function

maximize( Σ par_i × YTW_i + unique_issuers_selected × (investment / 100) )

Plain English: pick the combination that gets the most yield-weighted return, with a small bonus for including more distinct issuers. The bonus size is roughly "each unique issuer is worth ~$5,000 of yield on a $500k ladder," calibrated so diversification nudges the solver without dominating the yield objective.

5. Possible outcomes

When a ladder fails: diagnostics + one-click retry

An infeasible combination of constraints returns a FAILED ladder instead of silently producing nothing. The FAILED result page is designed to tell the advisor exactly what was asked for and why it didn't work, never a generic "try relaxing constraints" message.

What you see on every ladder result page

Every completed ladder (or partial ladder) renders the following sections, in this order:

What's not in this version yet

Honest disclosure so expectations match reality:

Where to find the numbers

All "magic numbers" that shape ladder results live in optimizer/constants.py in the repo at rk-stacks/vesper. That file is the single place to adjust AMT thresholds, scoring weights, solver timeouts, and default in-state percentages.

The tax-year bucket for the AMT threshold (currently 0.32 for 2024/2025 brackets) should be reviewed annually as brackets shift.

Changelog

Append-only history of what shipped, when, and in which deploy. The sections above always describe the current product; this log records how it got there. Each entry cites the Cloud Run revision and git commit so it can be verified. New entries are added at the bottom and existing ones are never edited.

Advisor review flags on ladder results2026-06-01vesper-web-00033003a9ce
Request: "Build warnings before building more optimization... The product should not only optimize. It should warn," with a specific list: state concentration, poor-credit jurisdiction, thin inventory, callable, AMT, tax-deferred, liquidity mismatch, HY suitability, and "not executable without market verification." Built: a data-driven warnings section on every completed ladder covering state concentration, weaker-credit jurisdictions, call risk, AMT-subject bonds, and below-investment-grade or unrated holdings, plus standing notices on the taxable-account assumption and market verification. Each flag fires only on its trigger. A liquidity flag was prototyped and pulled: it relied on a synthetic score with no real-world meaning, so it returns once real bid/ask data lands, with an advisor-set threshold.
Output provenance & audit logging2026-06-01vesper-web-00035bbefa0a
Request: "Treat compliance as product architecture, not legal cleanup. Audit logs, disclaimers, assumptions, timestamps... built into the product from the start." Every output should answer: what assumptions were used, what data source, when it was current, whether it's educational, illustrative, or actionable, and what the advisor must verify. Built: a "Provenance & assumptions" footer on every completed ladder (generated date, solver outcome, the tax rates used, AMT filter status, the $25k position floor, the data source, and a verify-before-acting checklist), plus audit-log writes that record every ladder build (success and failure) with the user, ladder, outcome, IP, and user-agent.
Effective-rate tax comparison2026-06-02vesper-web-000378c4d13e
Request: "It's not really the federal tax bracket, it's the effective tax rate, which is often lower." Built: an optional effective federal rate on the client. When present, the break-even banner shows a second, more conservative comparison at that lower rate and leads with it, keeping the marginal rate as the upper bound. This is display-only: bond selection still maximizes pre-tax yield, the AMT filter still keys off the marginal bracket, and NIIT is not applied (tax-exempt muni interest is excluded from it). Full mechanics are in the "Marginal vs. effective rate" note in the TEY section above.
Compare to a fund2026-06-02vesper-web-000424f91267
Request: "ETF/MF vs. ladder analyzer for client conversion and reviews." Built: an optional "Compare to a fund" panel on the result page. The advisor enters a muni ETF or mutual fund (ticker, yield, expense ratio) and gets a side-by-side against the ladder on yield-after-fees, state taxes, liquidity, behavior when rates rise, and control. Advisor-entered and illustrative; it never changes the ladder. An automatic fund-data feed (by ticker) is planned as a follow-on.
GO vs Revenue mix2026-06-02vesper-web-000447e1423d
Request: "Add: ability to delineate between GO and revenue bonds?" Built: a par-weighted bond-type line under the metric cards showing what share of the ladder is General Obligation (backed by the issuer's taxing power) vs Revenue (backed by a specific revenue stream). Display-only for now; constraining a ladder by type (e.g. a minimum % GO) would be a follow-on.
NIIT in the break-even2026-06-02vesper-web-000462a6a791
Request: "Should it account for NIIT?" Built: the taxable-equivalent break-even now shows a NIIT-adjusted figure. A client subject to the 3.8% Net Investment Income Tax would owe it on a taxable bond's interest but not on tax-exempt muni interest, so the yield a taxable alternative needs to keep up is higher. Display-only and conditional (the advisor knows whether the client is NIIT-subject); it does not change the ladder.
Edit and delete clients and ladders2026-06-02vesper-web-000482f8a24d
Update: clients and ladders could be created but not changed or removed. Built:clients can now be edited and deleted (deleting a client also removes its ladders), and ladders can be deleted. Every edit and delete is written to the audit log. Ladders are not edited in place: to change a ladder's constraints you regenerate it.
Thin-inventory warning on completed ladders2026-06-03vesper-web-0005344aab33
Request: "Thin inventory risk" (from the warning list). It already showed on failed ladders; now it shows on ones that filled too. Built: a review flag for rungs that filled from very few eligible bonds (three or fewer at the rating floor and maturity window), where the pick was effectively forced and sourcing or replacing the position may be hard. Names the rung and the count. The threshold is fixed for now; making it advisor-set is tracked.
Cash-need matching2026-06-24vesper-web-0005781110c3
Request: "The ladder solves for income currently, but may also need to solve for liquidity events/horizons beyond a simple 'rung tolerance' in certain circumstances: how would that work (other than self-selecting issue maturities manually)?" Built: a client can now record future cash needs (a label, amount, target year or range, and whether each need is required or a target). With cash-need matching turned on, the ladder schedules principal to arrive on or before those years instead of spreading evenly. Required needs are funded first where inventory allows; if a need cannot be fully funded the ladder still builds and reports the gap with a reason, rather than refusing. The result page shows a per-need funding summary, assumes early-returning principal earns a published money-market rate until it is used, and keeps the standard disclosure: this aligns scheduled maturities with stated needs, it does not guarantee liquidity or execution. High-yield is excluded from cash-need ladders, and equal-weight rungs turn off when matching is on.
Cash-need matching: call-risk and lump-rung labels2026-06-24vesper-web-00059af84bab
Refined from the 6/4 review. Two follow-ups to cash-need matching. First, when a matched need is funded partly by callable bonds, the funding panel now flags it: a callable bond can be redeemed before the need year, returning principal early and changing the expected cash-flow timing, so the advisor knows to verify call features. Second, when matching concentrates a large lump of principal in one year, that rung is now labeled "Sized for [need]" and accented, so the deliberately uneven rung reads as intentional rather than a lopsided ladder.
← Vesper homeContinue to dashboard →