Portfolio lots and harvesting: Claude skills backed by tested code

Analysis only. No tool in this server places orders, modifies custodian records or files returns. Every result is scoped to the accounts this principal is entitled to read; an account named in an argument is a request, not a grant. Unknown basis blocks a lot rather than defaulting to zero.

What taxagent-portfolio computes

9 commands, every one callable over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

CommandWhat it returnsWhat it needs
check_holdingsBuild a snapshot from an uploaded export and report the lots that cannot support a tax calculation: unknown basis, missing acquisition date, gaps in coverage. Run this first when a document is new.subject_ref, document_ref, as_of, prices, idempotency_key
review_rebalanceRebalance one account toward target weights, tax-aware. Never sells a security below its target, so with chunky lots it under-trades rather than overshooting and reports the residual drift. Withholds a purchase that would wash a loss sold inside the window.subject_ref, document_ref, as_of, prices, idempotency_key, target_weights
review_lotsGain and loss by lot with a wash-sale screen across the accounts in scope. Reports what is there; proposes nothing.subject_ref, document_ref, as_of, prices, idempotency_key
optimize_harvestThe same harvest problem as review_harvest, solved exactly rather than greedily. Where the search exhausts it proves no other selection raises the cash at lower tax cost. Slower, and it reports a certificate and an optimality gap.subject_ref, document_ref, as_of, prices, idempotency_key, cash_target
review_harvestLoss-harvesting scenario for one taxable account against a cash target, screened for wash sales. Produces a proposal for professional review, never a trade instruction.subject_ref, document_ref, as_of, prices, idempotency_key, cash_target
resolve_subjectResolve an account or household you are entitled to read and report its tax units. Pass document_ref on the first call of a session: account metadata comes from the document, and nothing resolves until one has been read.subject_ref
get_run_statusStatus of a run. Transport success is not workflow completion.run_id
get_scenarioRetrieve a stored evidence package by reference, including the rule pack and snapshot the run was pinned to.evidence_ref
get_capabilitiesWhich engines, asset classes and tax years this server covers for account-scope analysis, with each engine's limitations. Harvesting, exact optimization and rebalancing are separate engines making separate claims.nothing required

When to use it

Account-scope lot analysis: gain and loss, wash-sale screening, loss-harvest scenarios. This server carries an authenticated principal: an account named in an argument is a request to read it, never a grant.

Requests it answers:

  • “Which lots cannot support a tax calculation?”
  • “How do I rebalance without realizing a big gain?”
  • “What gains and losses are sitting in this account?”
  • “Can this harvest be proven the lowest-tax one?”
  • “Which losses can I harvest to raise cash?”
  • “Which accounts am I allowed to analyse?”
  • “Did that tax analysis actually finish?”
  • “Where is the evidence behind that result?”
  • “What does this server cover, and what does it decline?”

Skills that route here, each with the moment it applies:

gain-loss-review

The user asks what a sale would realize, how much gain they have taken this period, or how a disposal splits between short-term and long-term.

Calculate realized and unrealized gain and loss at lot level, split by holding period, from a frozen snapshot. Use when the user asks what they would realize by selling, where they stand on gains this year, or how a sale splits between short and long term.

harvest-review

The user wants to know what losses could be harvested, or wants to raise cash while keeping realized gains within a limit, in a taxable account.

Review tax-loss harvesting opportunities in one taxable account, with wash-sale screening and an explicit gain budget. Use when the user asks about harvesting losses, offsetting gains, or raising cash tax-efficiently. Produces scenarios for professional review, never a trade instruction.

household-coordination

A request spans several accounts of one household, or several sleeves of a UMA. Also use it whenever different managers may trade the same security for the same taxpayer.

Coordinate harvesting and cash raising across a household's accounts or a UMA's sleeves under one shared gain budget, with cross-account and cross-sleeve wash-sale prevention. Use when a request spans more than one account or manager, or when a UMA overlay must stop one sleeve from washing another's loss.

lot-review

Before relying on any gain, loss or harvest number, and whenever a result came back with `MISSING_BASIS`, `POSITION_MISMATCH` or `DUPLICATE_LOT`.

Check tax-lot completeness and integrity before any tax calculation — basis, acquisition dates, quantities, duplicates and position reconciliation. Use when lots have just been imported, a transfer arrived, or another skill reported blocked calculations.

rebalance-review

The user has approved targets and wants to move toward them, or wants to compare acting now against leaving the drift. Targets come from the advisor. This skill does not propose an allocation: an engine that inferred a target from the current holdings would be measuring drift against its own assumption.

Rebalance one account toward advisor-supplied target weights within a gain budget, showing the tax cost and the drift that remains. Use when the user asks what rebalancing would cost, or wants to compare acting now against leaving the drift.

tax-intake

No snapshot exists, or new source documents have arrived.

Turn uploaded statements and exports into source-cited, normalized tax facts with a missing-information register. Use at the start of any engagement, or when new documents arrive and no snapshot exists yet.

tax-profile

At the start of an engagement, and whenever scope is ambiguous.

Establish the household scope, tax units, account classifications and stated assumptions that every other tax skill depends on. Use before the first analysis of an engagement, or when ownership, registration or residency is unclear.

tax-review-pack

An analysis has completed and a person needs to review it, or a CPA needs a handoff.

Assemble a source-bound review package for a completed analysis — scope, assumptions, results, validation, limitations and version pins — for the responsible professional. Use when the user wants something to review, file, sign off, or hand to a CPA.

wash-sale-review

The user asks whether a loss will be disallowed, mentions buying back something they sold at a loss, or asks about the 30-day rule.

Screen proposed or completed loss sales for wash sales under IRC section 1091, across the accounts available, with explicit coverage gaps. Use when the user asks whether a loss is allowed, about the 30-day rule, or about repurchasing a security they sold at a loss.

What it does not do

Named here because leaving it out is how an agent ends up asserting something the code never computed. Each line is the registry's own wording, not a summary of it.

  • Greedy selection; no optimality claim.
  • No multi-period or risk-aware objective.
  • Optimal means optimal over the lots, prices and constraints supplied, for the stated objective: reach the cash target, realize as little gain as possible, use as few trades as possible. It is not a statement about what the account should do.
  • Risk, tracking error, a client's view on a position and the reason a lot was bought are outside the model entirely.
  • Beyond 40 budget-consuming lots the search is not exhausted. The status degrades to feasible and a gap is reported; optimality is never claimed from a truncated search.
  • Account scope only. Running it once per account is not a household optimum.
  • Drift is the sum of absolute differences between current and target weight. The engine never sells more of a security than its overweight, so with chunky lots it under-trades rather than overshooting, and residual drift is expected and reported.
  • Buys are allocated across underweight securities in proportion to their shortfall. A purchase has no tax consequence, so there is nothing to optimise about which lot it becomes.
  • A buy is withheld when the security was sold at a loss inside the wash-sale window, which leaves that drift open until the window closes.
  • Requires advisor-supplied target weights. Inferring a target from the current holdings would make the portfolio its own benchmark.
  • Account scope only, and no view on transaction costs, market impact, tracking error or which account should hold which exposure.

Where the figures come from

4 rules in the pack this server reads, each carrying its authority and legal effective date. The engines read from the pack, and a constant reappearing in the calculation path fails the build.

RuleAuthority
long_term_holding_period_yearsIRC section 1222(3); Rev. Rul. 66-7
permanently_disallowing_registrationsRev. Rul. 2008-5
replacement_matching_basisIRC section 1091(a)
wash_sale_window_daysIRC section 1091(a)

Engine coverage, and what each engine will not claim

3 engines behind these commands, straight from capabilities/engines.yaml. An engine with no passing reference test may not be advertised as implemented.

EngineStatusScopesTax yearsClaims optimality
rule-based-harvest-selectorimplementedaccount2026no
oracle-account-adapterimplementedaccount2026yes
rebalance-engineimplementedaccount2026yes

Scripted case coverage

14 of the 71 console cases call this server over JSON-RPC on every build, across 6 of the 15 client objectives the case inventory groups by: Cap realized gains, Entitlement, scope and replay, Harvest losses, Raise cash, Rebalance and Trust the data.

CaseWhat it asksWhat it showsOutcome
portfolio-discoverFinding what you are allowed to analyseThe first call of a session. Account metadata comes from the document, so it is named here; entitlements still decide the answer.completed
portfolio-deniedAn account in the document you are not entitled toThe document describes it, so it exists. It is still refused, and with the same wording as an account that does not exist at all.ACCESS_DENIED
portfolio-lotsGain and loss by lot, with a wash-sale screenWhat is held and what it carries. Proposes nothing: this is the tool for 'where do we stand', not 'what should we do'.completed_with_limitations
portfolio-harvestRaise $10,000 by harvesting lossesA proposal, the losses it realises, and the lots it held back — one for wash-sale risk, one for unknown basis.completed_with_limitations
portfolio-missing-nettingA gain budget with no netting basis'Keep gains under $15,000' reads two ways and they give different answers, so the schema requires the caller to say which.protocol_or_needs_input
portfolio-infeasibleA target the budget cannot reachThe shortfall is reported. The constraint is never quietly relaxed to manufacture a result that looks like success.INFEASIBLE_CONSTRAINTS
portfolio-planned-purchaseA purchase you have not placed yetScreening a planned buy before it happens turns a disallowed loss into a decision the advisor still gets to make.completed_with_limitations
portfolio-idempotentThe same key twice returns the same runRepeating a request must not produce a second analysis with a new evidence package. The key is the caller's guarantee.accepted
portfolio-household-refusedA household asked of the account serverRunning an account engine over a household is refused rather than approximated, and the refusal names what is available instead.UNSUPPORTED_SCOPE
portfolio-wrong-yearAn analysis dated to a year with no rule packNo reviewed pack covers 2019, so the run is blocked rather than computed from this year's rules under last decade's law.WRONG_RULE_YEAR
portfolio-intakeWhat in this document cannot be usedRun first on a new export. Lots with unknown basis are named here rather than silently treated as zero-basis later.completed_with_limitations
portfolio-rebalanceRebalance to target weights, tax-awareCloses drift within a gain budget, never selling a security below its target. With chunky lots it under-trades rather than overshooting, and reports the residual.completed_with_limitations
portfolio-rebalance-needs-targetsA rebalance with no target weightsRefused rather than inferred. Deriving a target from the current holdings would make the portfolio its own benchmark.protocol_or_needs_input
portfolio-no-executionThere is no tool that places a tradeNot disabled, not permissioned off — absent. A caller asking for one is refused at the boundary.UNSUPPORTED_SCOPE

Questions it answers

Which lots cannot support a tax calculation?

Build a snapshot from an uploaded export and report the lots that cannot support a tax calculation: unknown basis, missing acquisition date, gaps in coverage. Run this first when a document is new. It needs subject_ref, document_ref, as_of, prices and idempotency_key. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call check_holdings on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

How do I rebalance without realizing a big gain?

Rebalance one account toward target weights, tax-aware. Never sells a security below its target, so with chunky lots it under-trades rather than overshooting and reports the residual drift. Withholds a purchase that would wash a loss sold inside the window. It needs subject_ref, document_ref, as_of, prices, idempotency_key and target_weights. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call review_rebalance on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

What gains and losses are sitting in this account?

Gain and loss by lot with a wash-sale screen across the accounts in scope. Reports what is there; proposes nothing. It needs subject_ref, document_ref, as_of, prices and idempotency_key. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call review_lots on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

Can this harvest be proven the lowest-tax one?

The same harvest problem as review_harvest, solved exactly rather than greedily. Where the search exhausts it proves no other selection raises the cash at lower tax cost. Slower, and it reports a certificate and an optimality gap. It needs subject_ref, document_ref, as_of, prices, idempotency_key and cash_target. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call optimize_harvest on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

Which losses can I harvest to raise cash?

Loss-harvesting scenario for one taxable account against a cash target, screened for wash sales. Produces a proposal for professional review, never a trade instruction. It needs subject_ref, document_ref, as_of, prices, idempotency_key and cash_target. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call review_harvest on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

Which accounts am I allowed to analyse?

Resolve an account or household you are entitled to read and report its tax units. Pass document_ref on the first call of a session: account metadata comes from the document, and nothing resolves until one has been read. It needs subject_ref. The account or household the analysis is scoped to. Naming one is a request to read it, never a grant. Call resolve_subject on taxagent-portfolio over MCP. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

The other servers