Skip to content

Portfolio evolution — evolve the rule that governs the spend

Module: agentdescent.portfolio_evolve

portfolio_report is a fixed, honest reallocation policy: grow the layers that pay, shrink the ones that do not, hold the ones with too little evidence. The framework's thesis is recursion — the rule that decides how the framework spends should itself be improved by the framework. This module makes the policy evolvable.

from agentdescent import portfolio_evolve

report, result = portfolio_evolve(
    store,                       # or windows=[...], val_windows=[...]
    propose=offline_propose,     # rewrites the four numbers
    seeds=(0, 1, 2), val_seeds=(3, 4))
print(report["verdict"], report["val_gain"])   # adopt, +0.05 over the baseline
print(result.rendered)                          # the evolved parameter block

The rule is four numbers — the shipped rule with different numbers

ReallocationPolicy(floor, grow, shrink, min_samples) is the evolvable rule, and apply(rows) delegates to portfolio_report rather than re-implementing it. The default values are portfolio_report's own defaults, so ReallocationPolicy() is the fixed policy the framework ships — the baseline and the evolved rule cannot drift apart. reallocation_spec() is the ParamSlot that compiles the four numbers.

A window is the portfolio's own unit

LayerBudget rows are exactly what portfolio_report consumes; a window is a set of them. A problem's reward is the projected return the policy's reallocation would buy: each layer's measured return scaled by its budget ratio under the policy, averaged over the window. The projection is not clipped (the default auc reward still normalises); a custom meta_reward scores it in the search and in validation, so the knob drives what gets adopted rather than only the scorecard. windows_from_archive(store) cuts the archive's rule records into contiguous windows (aggregated per slot, records without a slot skipped) and splits them positionally into train and validation, so the evolved rule is judged on windows it never trained on. Callers with their own history can pass windows= / val_windows= directly.

What the report says

The evolved policy is measured against the fixed baseline (ReallocationPolicy()) on both splits. The report carries the per-window validation, the mean train/val gain over the baseline, and an adopt/reject verdict (adopt only when the validation gain is strictly positive; a refused no-op is reject, not an adoption). An out-of-bounds proposal (outside the ParamSlot bounds) is refused by the gate, so the artifact stays the fixed baseline rather than committing a broken rule.