API reference¶
Every name agentdescent exports, grouped by the module it comes from.
Generated from the package's own signatures and docstrings by
python -m tools.gen_api_docs — tests/test_api_reference.py fails if this
page and the code disagree, so a signature here is the signature you get.
A signature too long for its heading is printed in full below it, one
parameter per line, followed by a table of what each one does — type,
default, and the docstring's own prose. *required* in the default column
means the parameter has none.
Each section links to the page that explains why the module is shaped the way it is; this page is the what.
545 public names across 107 modules.
The loop¶
evolve(), the artifact, the actor, and what a run returns. · agentdescent.evolution · guide
Agent¶
Convenience actor: bundles running a task and proposing an improvement.
EvolutionResult(...)¶
EvolutionResult(
state: Dict[str, str],
rendered: str,
final_reward: float,
history: List[RoundInfo],
ledger_log: List[str],
error: Optional[str] = None,
stop_reason: str = 'rounds',
forced_refreshes: int = 0,
stragglers: int = 0,
resumed: int = 0,
retired_workers: int = 0,
audit_drained: int = 0,
replay: Optional[Dict[str, Any]] = None,
curriculum: Optional[Dict[str, Any]] = None,
meta: Optional[Dict[str, Any]] = None,
lineages: Optional[List[Dict[str, Any]]] = None,
usage: Usage = <factory>,
wallclock: float = 0.0,
rollouts: int = 0,
rollout_seconds: float = 0.0,
eval_seconds: float = 0.0,
merge_seconds: float = 0.0,
merge_gate_seconds: float = 0.0,
worker_starved_seconds: float = 0.0,
evals_skipped: int = 0,
bounded_scans_cut: int = 0,
stale_considered: int = 0,
stale_discarded: int = 0,
redispatched: int = 0,
duplicates_dropped: int = 0,
cas_conflicts: int = 0,
critic_checked: int = 0,
critic_rejected: int = 0,
cache_hits: int = 0,
cache_misses: int = 0,
trajectories_recorded: int = 0,
trajectories_dedup: int = 0,
trajectory_write_seconds: float = 0.0,
trajectory_write_failures: int = 0,
trajectories_path: Optional[str] = None,
invariant_checked: int = 0,
invariant_rejections: int = 0,
sandbox_wait_s: float = 0.0,
sandbox_setup_s: float = 0.0,
sandboxes_created: int = 0,
sandboxes_reused: int = 0,
sandbox_failures: int = 0,
fusion_trials: List['FusionTrial'] = <factory>,
budget: Optional[Dict[str, Any]] = None
) -> None
| method | what it does |
|---|---|
cost_summary() -> str |
One line: what the run cost. Complements outcomes(), which says why it went as it did. |
cost_to_quality(target: float) -> Optional[int] |
Rollouts spent up to the first round that reached target. |
duplicate_rate() -> float |
Cache hits as a fraction of lookups -- work that did not have to be redone. In one process this is memoisation working; across processes it is the figure that says how much a shared cache would be worth. |
fusion_stats() -> 'FusionStats' |
How often merging beat the best single diff -- and how badly it lost. |
gate_share() -> float |
How much of the merger's busy time went to evaluation, in [0, 1]. |
load(path: str) -> 'EvolutionResult' |
Read back a result written by save. |
merger_occupancy() -> float |
Merger busy time over wall-clock. Above ~0.8 it is the critical path. |
outcomes() -> Dict[str, int] |
Merge outcomes for the whole run, by category -- why it went as it did. |
save(path: str) -> None |
Write the evolved artifact and its run summary to a JSON file. |
stale_rate() -> float |
Discarded evidence as a fraction of evidence considered; 0.0 if none. |
time_to_quality(target: float) -> Optional[float] |
Wall-clock at the first round whose held-out reward reached target. |
write_to(...) |
Install a file-tree artifact back into a real directory. |
EvolvingArtifact(...)¶
An Evolvable: flat state + a strategy.
EvolvingArtifact(
id: str,
state: Optional[Dict[str, str]] = None,
version: int = 1,
blast_radius: float = 0.2,
runtime: Optional['_Runtime'] = None,
strategy: Optional[Strategy] = None,
contract: Optional[Contract] = None
) -> None
| method | what it does |
|---|---|
cheap_eval(evidence: EvidenceCard) -> float |
Score this artifact on the trajectories an evidence card carries. |
evidence_eval(evidence: EvidenceCard) -> float |
Score this artifact on the trajectories an evidence card carries. |
full_eval(task_set: Sequence[Task]) -> Dict[str, float] |
Score on a task set. No longer part of the Evolvable protocol -- the engine reaches ground truth through the verifier's eval_fn -- and kept because it is a convenient thing for a caller to have. |
score(tasks: Sequence[Task]) -> float |
Mean reward over tasks, evaluated concurrently. |
score_bounded(tasks: Sequence[Task], floor: float) -> float |
Mean reward, abandoned once it provably cannot exceed floor. |
FusionStats(...)¶
The fusion tournament's record, with every denominator it needs.
FusionStats(
trials: int = 0,
contested: int = 0,
unranked: int = 0,
single_candidate: int = 0,
contradiction: int = 0,
nothing_to_fuse: int = 0,
dominant_single: int = 0,
synthesis_failed: int = 0,
synthesized_wins: int = 0,
fused_wins: int = 0,
single_wins: int = 0,
neither: int = 0,
ties: int = 0,
mean_gain: float = 0.0,
negative: int = 0,
mean_loss: float = 0.0,
worst_loss: float = 0.0,
below_baseline: int = 0
) -> None
| method | what it does |
|---|---|
summary() -> str |
One line, and it says when there is nothing to report. |
LLMAgent(...)¶
Adapt a Completion (from agents) into an Agent.
LLMAgent(
complete: Completion,
solve_template: str = 'You are executing an artifact defined below.\n\n{artifact}\n\nApply it to this input and output ONLY the result, nothing else.\n\nInput:\n{prompt}',
propose_template: str = "The artifact just failed a task (score {reward:.2f} out of 1.0).\n\nArtifact so far:\n{artifact}\n\nTask input:\n{prompt}\n\nIt produced:\n{output}\n{expected}\nPropose exactly ONE concise, general rule (a single imperative sentence) to improve the artifact for this and similar cases. State the rule in general terms -- it will be applied to other tasks, so do NOT mention this task's specific values or answer. Output only the rule text, or NONE if no rule would help.",
show_meta: bool = True,
meta_chars: int = 600,
_empty_replies: int = 0
) -> None
ProposalContractError¶
propose returned something that is not text (or None).
RewardContractError¶
The caller's reward returned something outside the documented contract.
RoundInfo(...)¶
RoundInfo(
round: int,
held_out_reward: float,
n_items: int,
committed: int,
rejected: int,
reasons: Dict[str, int] = <factory>,
elapsed_s: float = 0.0,
rollouts: int = 0,
calls: int = 0,
tokens: int = 0,
considered: int = 0,
discarded_stale: int = 0,
conflicts_dropped: int = 0,
fused: int = 0,
propose_errors: int = 0,
propose_error: Optional[str] = None
) -> None
Task(id: str, prompt: str, meta: Dict[str, Any] = <factory>) -> None¶
One unit of work the artifact is evaluated on.
claude_agent(model: str = 'claude-opus-4-8', max_tokens: int = 1024) -> LLMAgent¶
Convenience: LLMAgent(claude(model)) (provider code lives in agents).
evolve(...)¶
Evolve an artifact. Provide either agent (with solve/propose) or the run / propose callables directly.
evolve(
tasks: Sequence[Task],
reward: Reward,
*,
agent: Optional[Agent] = None,
run: Optional[Run] = None,
propose: Optional[Propose] = None,
strategy: Optional[Strategy] = None,
parallel: Optional['ParallelStrategy'] = None,
task_sampler: Optional['TaskSampler'] = None,
initial_state: Optional[Dict[str, str]] = None,
blast_radius: float = 0.2,
artifact_id: str = 'artifact',
rounds: int = 15,
n_workers: int = 4,
max_concurrency: int = 1,
refresh_interval: int = 1,
round_timeout: Optional[float] = None,
target_reward: Optional[float] = None,
patience: Optional[int] = None,
max_worker_errors: int = 3,
eval_concurrency: int = 8,
asynchronous: bool = False,
async_ratio: int = 3,
resync_on_commit: bool = True,
pipelined_gate: bool = False,
gate_workers: int = 2,
max_seconds: Optional[float] = None,
max_rollouts: Optional[int] = None,
max_calls: Optional[int] = None,
max_tokens: Optional[int] = None,
stop_on_diminishing_returns: bool = False,
efficiency_floor: float = 0.25,
call_budget: Optional['CallBudget'] = None,
self_verify: bool = True,
held_out_frac: float = 0.4,
repo_path: Optional[str] = None,
checkpointing: bool = False,
agg_config: Optional[AggregatorConfig] = None,
staleness_policy: Optional[StalenessPolicy] = None,
aggregator_factory: Optional[AggregatorFactory] = None,
oracle_budget: int = 200,
cheap_eval_tasks: Optional[int] = None,
fusion_tournament: Optional[bool] = None,
solved_threshold: float = 0.999,
shuffle: bool = False,
seed: int = 0,
on_round: Optional[Callable[['RoundInfo'], None]] = None,
on_reports: Optional[Callable[[Sequence[Any]], None]] = None,
stop_when: Optional[Callable[['RoundInfo'], bool]] = None,
verbose: bool = False,
usage: Optional[Usage] = None,
policies: Optional['Policies'] = None,
allocator: Optional[Any] = None,
record_trajectories: bool = False,
trajectory_store: Optional['TrajectoryStore'] = None,
contract: Optional[Contract] = None,
extra_artifacts: Optional[Dict[str, 'EvolvingArtifact']] = None,
population_islands: int = 0
) -> EvolutionResult
| parameter | type | default | what it is |
|---|---|---|---|
tasks |
Sequence[Task] |
required | The work the artifact is evaluated on. Split into train / held-out by position -- the last held_out_frac of the sequence is held out, in the order given. At least 4 are required and ids must be unique. |
reward |
Reward |
required | (task, output) -> [0, 1]. Scores in [0, 1]; the engine treats >= solved_threshold as a pass (no proposal is requested). |
agent |
Optional[Agent] |
None |
An object with solve + propose. Provide this or run and propose; both signatures are checked before the first rollout. |
run |
Optional[Run] |
None |
run(rendered, task) -> output and propose(rendered, task, output, reward) -> str \| None. |
propose |
Optional[Propose] |
None |
As run. |
strategy |
Optional[Strategy] |
None |
How the artifact is represented and how a proposal becomes a Diff. |
parallel |
Optional['ParallelStrategy'] |
None |
How a round's tasks are partitioned across workers. DataParallel (default) shards them; TensorParallel(n_sections, keys=, route=) also gives each worker a disjoint section of the artifact and rejects out-of-section edits -- counted as section-violation in outcomes. The pairing is validated before the first rollout: a strategy with no declared key space (AppendRules) or fewer keys than sections is refused rather than silently dropping most of its proposals. PipelineParallel raises (see above). |
task_sampler |
Optional['TaskSampler'] |
None |
Which task a worker rolls out next, from its shard. Defaults to RoundRobin; use DifficultyWeighted to spend rollouts on tasks that still carry a learning signal. |
initial_state |
Optional[Dict[str, str]] |
None |
Seed the artifact instead of starting from strategy.initial(). Ignored when resuming an existing repo_path. |
blast_radius |
float |
0.2 |
Governance layer, in [0, 1] (see above). |
artifact_id |
str |
'artifact' |
Name of the evolving artifact; becomes a filename, so it must match [A-Za-z0-9_.-]+. |
rounds |
int |
15 |
Number of round barriers to run. Under asynchronous=True this becomes a worker-rollout budget of rounds * n_workers instead. |
n_workers |
int |
4 |
Workers per round (>= 1). |
max_concurrency |
int |
1 |
How many of them actually run at once (see above). |
refresh_interval |
int |
1 |
How many rounds a worker keeps its ledger snapshot before taking the round's fresh one. 1 (default) is what this loop always did: every worker proposes against the current head, so a diff's staleness eta is 0 by construction -- and that made staleness_policy= a knob with nothing to decide on this path (measured over an 8-round run: all 15 staleness decisions saw eta=0 and returned ACCEPT, so Full, Guarded and Reflective were indistinguishable). Above 1, workers hold a spread of versions -- the refresh is staggered by worker id -- so their diffs arrive with a spread of eta and the staleness policy, the alpha tolerances in agg_config and the all-stale outcome all become reachable synchronously. Costs no extra ledger read: a worker either adopts the snapshot the round already took, or keeps the older one it has. Ignored under asynchronous=True, where the lag budget is async_ratio. |
round_timeout |
Optional[float] |
None |
Seconds a round will wait for its concurrent workers before giving up on the slow ones. None (default) waits forever, which is what you want when every rollout is bounded -- but a single hung rollout then stalls the run, because the aggregator is a barrier. Abandoned work keeps running in the background (Python cannot cancel a thread) and is simply not waited for; it is reported when verbose. Only applies when max_concurrency > 1. |
target_reward |
Optional[float] |
None |
Stop as soon as held-out reward reaches this. Without it a run always spends all rounds, including after it has converged -- measured at 43% of rollouts wasted on an artifact that had stopped changing. |
patience |
Optional[int] |
None |
Stop after this many consecutive rounds with no improvement in held-out reward. None disables it. Cheap insurance for a run that plateaus below target_reward. |
max_worker_errors |
int |
3 |
How much total failure to tolerate before giving up -- and only while no worker has ever completed a rollout, which reads as a misconfiguration (wrong key, dead endpoint). Once any worker has succeeded the backend demonstrably works, so failures are treated as transient and the run continues on whatever evidence it did gather. Counts consecutive failed rollouts per worker on the async path (see result.retired_workers) and consecutive rounds in which every worker failed on the sync path. |
eval_concurrency |
int |
8 |
How many held-out tasks to score at once. Every gate goes through this -- each round's measurement and, far more often, the aggregator's per-candidate comparisons -- so it is the merge half of the run's parallelism, independent of n_workers. 1 restores the old sequential behaviour. |
asynchronous |
bool |
False |
Delegate to async_evolve -- no round barrier, with async_ratio as the staleness lag budget. |
async_ratio |
int |
3 |
As asynchronous. |
resync_on_commit |
bool |
True |
Asynchronous path only. Refresh every worker's snapshot as soon as a sweep commits, so no one starts a rollout against a superseded artifact. See async_evolve, which documents what it does and does not fix -- a commit landing mid-rollout still produces a stale card. |
pipelined_gate |
bool |
False |
Under asynchronous=True, run a merge's measurement phase on its own threads instead of on the merger, so the merger goes back to draining while the gate runs. Off by default; documented in full on async_evolve, which implements it. Warns and does nothing on the synchronous path, where the round barrier idles every worker for the whole merge regardless. |
gate_workers |
int |
2 |
As pipelined_gate. |
max_seconds |
Optional[float] |
None |
Wall-clock budget. None (default) means unbounded; the async path uses 20.0 when unset. |
max_rollouts |
Optional[int] |
None |
The budget in the two units a comparison has to hold fixed: rollouts completed, and actor invocations (run + propose). rounds is not one of them -- configurations differ in how much model a round buys, so a budget fixed in rounds hands the wider configuration more model and then reports the extra model as a win for parallelism. Either bound stops the run with stop_reason "max_rollouts" / "max_calls". Checked at the round barrier, so a run overshoots by up to one round. A round is dispatched or it is not; stopping halfway would leave a half-merged round, and the states a comparison compares are the ones a merge produced. So a budget is a bound on where to stop, never the number to compare on: read the spend the run actually reported (result.rollouts, result.usage.calls), which is what baselines does -- it refuses to call two arms equal-budget when their measured spends differ. The async path has no barrier and enforces both per rollout, so it overshoots by at most the rollouts already in flight. |
max_calls |
Optional[int] |
None |
As max_rollouts. |
max_tokens |
Optional[int] |
None |
Hard cap on total tokens consumed (prompt_tokens + completion_tokens). A reasoning model can spend 40k tokens on hidden thinking in one call, so max_calls and max_rollouts do not bound cost: a 20-round run with 4 workers is 80 rollouts and ~160 model calls, but at 40k tokens each that is 6.4M tokens -- the bill, not the count, is what a deployment needs to control. Checked at the round barrier alongside the other budgets; the async path checks per-rollout for tighter control. Stops with stop_reason="max_tokens". None (default) means unbounded. |
stop_on_diminishing_returns |
bool |
False |
Stop when the run's own return per token has fallen off its peak by more than efficiency_floor. The economic rule: keep buying compute while it pays, stop when it does not -- even with budget left. Off by default, because a run whose reward only rises late would be cut short by it, and because it needs max_tokens (return per token needs a cost to divide by). Stops with stop_reason="diminishing_returns". |
efficiency_floor |
float |
0.25 |
How far below the peak counts as diminishing, when the stop above is on. Self-calibrating against the run's own best rate, so it is a ratio, not an absolute quantity: a reward is in [0, 1] and a token count is in the millions, and their quotient has no interpretable scale. |
call_budget |
Optional['CallBudget'] |
None |
An adaptive per-call thinking budget (o1-style test-time scaling). Pass a CallBudget whose base matches the adapter's configured max_tokens, and the engine adjusts the per-call ceiling based on each parent's score and the remaining budget — promising parents get more thinking, dead-end ones get less, and the allocation tightens as the token budget is spent. The adapter must be wrapped with budgeted_completion for this to reach it. None (default) means the per-call ceiling is the adapter's own and never changes. |
self_verify |
bool |
True |
Re-run the trajectory with the diff applied to record a local before/after delta. Doubles the rollouts spent per proposal; ports that score candidates only on held-out should pass False. |
held_out_frac |
float |
0.4 |
Fraction of tasks reserved for held-out scoring, in (0, 1). |
repo_path |
Optional[str] |
None |
Where the git-backed ledger lives. Omit for a throwaway repo that is removed when this call returns (not held until interpreter exit, so a sweep does not accumulate one git repo per run); passing the same path again resumes that ledger, and a caller-supplied path is never deleted. Git runs with an isolated config, so a personal ~/.gitconfig (commit.gpgsign, core.hooksPath) cannot fail the ledger's own bookkeeping commits. |
checkpointing |
bool |
False |
Save the aggregator's in-memory search state to <repo_path>/checkpoints/ after every round, so a later run on the same repo_path resumes the search instead of re-deriving it from the ledger head. repo_path alone already resumes the artifact; what it cannot carry is what the search learned on the way there -- Beta posteriors, a population archive and its selected counts, the early-stop patience counter. Off by default, because a run that never resumes (a scratch repo, a tempdir) would pay the serialise-and-write for nothing, and because an aggregator opts in by implementing checkpoint() / restore() at all -- one without them is unaffected either way. A checkpoint that cannot be written or read (state that does not serialise, a lock another process holds, a full disk) is skipped, never raised: the cost of a missing checkpoint is a resume that starts the search fresh, which is what False does. |
agg_config |
Optional[AggregatorConfig] |
None |
Tuning for the reference aggregator (batching, acceptance risk, trust region, staleness tolerance). |
staleness_policy |
Optional[StalenessPolicy] |
None |
What to do with a diff proposed against an out-of-date version -- full / guarded (default) / reflective. |
aggregator_factory |
Optional[AggregatorFactory] |
None |
Replace the optimizer entirely; receives (ledger, verifier, audit, config, staleness_policy). |
oracle_budget |
int |
200 |
Hard cap on full held-out oracle evaluations during audits. Once spent, the verifier falls back to its cheap layer -- which only saves anything when cheap_eval_tasks makes that layer genuinely cheaper, so the two knobs go together. |
cheap_eval_tasks |
Optional[int] |
None |
How many held-out tasks the cheap layer scores when the aggregator is merely ranking candidates -- conflict resolution, and the fusion tournament when it is on. None (default) is 8, or the whole held-out set when that is smaller. It used to mean the whole set unconditionally, which made the cheap layer cost exactly what the oracle costs: ranking one candidate bought a full sweep of real agent calls, and oracle_budget's fallback saved nothing because it was the same measurement. Nothing in bench/ or examples/ ever passed this, so every real run paid it. The cost of the new default is ranking resolution: 8 binary-scored tasks resolve 0.125, so two candidates closer than that are ordered by whichever the sample happens to favour. That is bounded to which candidate goes forward -- both commit gates read eval_counts on the full set, so it cannot decide whether a change is safe. Pass len(held_out) to restore the exact behaviour. The sample is fixed for the run, so candidates are always compared like-for-like. |
fusion_tournament |
Optional[bool] |
None |
Rank the surviving diffs against their fusion before putting one forward. None (default) defers to agg_config, which is off. Off, because the ranking is paid every round while the only decision it changes from the acceptance gate's is recoverable: the union is a superset of every single diff, so committing it unranked loses no proposal. DefaultFusion carries the case analysis. On, because it is the only way to measure win_rate -- best_single_score exists only where a single was actually scored. That number is a property of the workload, not of the mechanism, so it is worth measuring per workload and not worth paying for on every run. |
solved_threshold |
float |
0.999 |
A reward at or above this counts as solved, so no proposal is requested and the task sampler counts a pass. The default (SOLVED, 0.999) is right for a binary scorer. Lower it for a graded one -- a ROUGE score or an LLM judge rarely reaches 0.999, so every rollout would ask the reflector to "fix" an answer that scored 0.95, and the run reports below-threshold as if the reflector were the problem. |
shuffle |
bool |
False |
Shuffle tasks before that positional split. Off by default, which keeps a run reproducible and keeps val_frac's promise that the engine's held-out split is exactly that Dataset's val. Turn it on for grouped data -- anything ordered by category, source, difficulty or date -- where the tail of the file is a different distribution from the head, and every gate in the run (the acceptance test, target_reward, final_reward) would then be measured against it. |
seed |
int |
0 |
As shuffle. |
on_round |
Optional[Callable[['RoundInfo'], None]] |
None |
Called with each RoundInfo as the round completes -- progress for a long run, which otherwise reports nothing until it returns. An exception raised here is reported but does not abort the run. |
on_reports |
Optional[Callable[[Sequence[Any]], None]] |
None |
Called with each round's MergeReports, before on_round reduces them to counters. Each report names the diff the merger measured (candidate_diff), so a caller can keep rejected candidates for a diagnosis instead of losing what a run that learned nothing proposed (#452). An exception raised here is reported, not fatal. |
stop_when |
Optional[Callable[['RoundInfo'], bool]] |
None |
Called after on_round with the same RoundInfo; return True to end the run with stop_reason="stop_when". This is the seam for a budget the engine does not know how to count -- dollars from a shared Usage, an external deadline, a kill file. It is asked where max_seconds / max_calls are, so it stops between rounds and never mid-merge, and the run keeps what it has committed. An exception raised here is reported, not fatal. |
verbose |
bool |
False |
Print a line per round. Independent of the RuntimeWarning emitted when a run ends early -- that always fires. |
usage |
Optional[Usage] |
None |
Share one Usage with your model adapters (claude(usage=u), openai_compatible(usage=u)) and the result's token counts become real. Without it the run still reports calls, seconds and failures -- run is (rendered, task) -> str, so an opaque actor has no way to surface tokens, and inventing a number would be worse than reporting zero. |
policies |
Optional['Policies'] |
None |
Bundle of replaceable pieces (Policies). Every field defaults to None meaning "current behaviour", so Policies() and passing nothing are the same run. The individual keyword arguments -- task_sampler, staleness_policy, aggregator_factory -- are shortcuts onto its fields and keep working; an explicit argument wins over a bundle default rather than being silently ignored. Fields whose implementations have not landed yet raise rather than being accepted and ignored: a caller who passes a custom acceptance rule and sees a finished run would reasonably conclude it ran. New capabilities go here rather than adding another parameter to a function that already has thirty-five. |
allocator |
Optional[Any] |
None |
Value-directed budget allocation (see allocator). None (the default) is the old behaviour exactly: optional spend (fusion tournaments, the self-verify rollout) degrades against the global soft_floor / hard_floor thresholds as the token budget runs out, every candidate treated alike. Pass a ValueBudgetAllocator (or any object with decide(ctx) -> AllocateDecision and observe(ctx, committed)) and, once the budget is tight, the self-verify rollout is spent only on candidates the allocator's value model expects to commit; the merge path reports every outcome back so the model learns. Requires max_tokens to be set (the allocator decides how the budget is spent; max_tokens is what exists to spend). Counterfactual exploration re-measures candidates the model judged unworthy, so a biased value model is corrected rather than reinforced. |
record_trajectories |
bool |
False |
Persist every rollout and gate evaluation as a Trajectory under <repo_path>/trajectories/ -- what the run provider saw, what it returned, what that earned, against which artifact version and environment. Off by default: a throwaway repo's records die with it, so the write cost is pure overhead there, and recording is a deliberate choice about keeping a run's evidence. The store is the durable base every learning-type increment reads -- process-supervised reflection, offline re-weighting, audit replay -- and it is what an EvidenceCard.trace_ids entry points at. Pass trajectory_store= with a store you built to control where records land instead. |
trajectory_store |
Optional['TrajectoryStore'] |
None |
An explicit TrajectoryStore to record into. Pass one to control where records land (a shared store, a custom root, a custom privacy configuration) without going through the <repo_path>/trajectories/ default. The explicit store wins over record_trajectories; None + record_trajectories builds the default store. |
contract |
Optional[Contract] |
None |
The artifact's contract -- the externally-visible interface, and where governance invariants are declared. Pass Contract(invariants=(Invariant("mutable_keys", ("a", "b")),)) and the aggregator enforces each rule before a candidate is measured (see invariants); a diff that breaks one reports a violates-invariant outcome and never buys a held-out sweep. None (the default) is the reference skill contract, which declares nothing and enforces nothing -- the untouched path exactly. |
extra_artifacts |
Optional[Dict[str, 'EvolvingArtifact']] |
None |
Additional artifacts to register alongside the primary (artifact_id), as {aid: EvolvingArtifact}. The primary is the one the worker loop rolls out and proposes on; each extra is registered in the ledger and the worker proposes against it too, so a proposal can name one of them as a diff target and the aggregator merges it there (the evidence buffer already buckets by diff.target). depends_on on an extra's Contract declares which artifacts it relies on: a contract-breaking change to one re-measures its dependents (their cached scores were taken under the superseded contract) and lands atomically via Ledger.commit_atomic. Empty (the default) is the old single-artifact behaviour exactly. |
population_islands |
int |
0 |
How many persistent head-islands the population explores in parallel, on top of dev (see PopulationAggregator). Requires a declared selection policy (Policies(selection=...)). 0 (the default) keeps the old behaviour exactly: per-step forks that reset a policy's lineage. Above zero, island_count forks survive across steps and each keeps its own lineage, so tree-shaped policies (CladeThompson, FlatPuct) score clades -- the metaproductivity signal HGM's population search is built on. Islands checkpoint and resume like the rest of the population archive. |
reflector(...)¶
Use any model as the reflector for an agent you already have.
reflector(
complete: Completion,
template: str = "The artifact just failed a task (score {reward:.2f} out of 1.0).\n\nArtifact so far:\n{artifact}\n\nTask input:\n{prompt}\n\nIt produced:\n{output}\n{expected}\nPropose exactly ONE concise, general rule (a single imperative sentence) to improve the artifact for this and similar cases. State the rule in general terms -- it will be applied to other tasks, so do NOT mention this task's specific values or answer. Output only the rule text, or NONE if no rule would help.",
show_meta: bool = True
) -> Propose
tasks_from(...)¶
Turn a list of dicts -- a dataset -- into Task objects.
tasks_from(
rows,
prompt: str = 'prompt',
gold: str = 'gold',
id: Optional[str] = None,
**meta_keys: str
) -> List['Task']
Meta-evolution¶
Evolve a decision slot of evolve() itself, and validate it elsewhere. · agentdescent.meta · guide
ContinuousMetaReport(...)¶
What one continuous_meta_run session did, and why.
ContinuousMetaReport(
slot: str,
seed_value: str,
value: str,
validation: Dict[str, Dict[str, float]],
mean_gain: float,
transfer: Optional[float],
verdict: str,
reason: str,
seeded_from: Optional[str],
fresh_seed_value: str,
fresh_gain: float,
fresh_transfer: Optional[float],
archived: bool,
archived_run_id: Optional[str],
archive_count: int
) -> None
MetaOutcome(...)¶
What one inner run did under a candidate slot value.
MetaOutcome(
curve: List[float] = <factory>,
final: float = 0.0,
rollouts: int = 0,
detail: Dict[str, Any] = <factory>
) -> None
| method | what it does |
|---|---|
from_result(result: EvolutionResult, **detail: Any) -> 'MetaOutcome' |
Read an inner EvolutionResult. |
MetaRunReport(...)¶
What a meta_run decided, and why.
MetaRunReport(
slot: str,
seed_value: str,
value: str,
validation: Dict[str, Dict[str, float]],
mean_gain: float,
transfer: Optional[float],
verdict: str,
reason: str
) -> None
ParamSlot(...)¶
The numeric hyper-parameters of any policy class, one key each.
ParamSlot(
factory: Callable[..., Any],
params: Mapping[str, float],
bounds: Mapping[str, Tuple[float, float]] = <factory>,
title: str = '# Policy parameters',
invalid_proposals: int = 0,
_lock: threading.Lock = <factory>
) -> None
PrioritySelection(...)¶
A SelectionPolicy driven by a priority rule.
PrioritySelection(
source: str = 'def priority(rank, visits, total, prior, depth, n_nodes):\n # Flat PUCT (ERA, futs.py): exploit by rank, explore by visit count.\n c = 1.0\n return rank + c * (1.0 / n_nodes) * math.sqrt(total) / (1 + visits)\n'
) -> None
SlotSpec¶
A Strategy that also compiles.
SourceSlot(...)¶
One slot of validated source, compiled by build.
SourceSlot(
initial_value: str = '',
key: str = 'value',
empty_render: str = '(no instruction yet)',
min_chars: int = 1,
validate: Optional[Callable[[str], str]] = None,
build: Optional[Callable[[str], Any]] = None,
description: str = 'The value is source text; reply with the complete revised text.',
invalid_proposals: int = 0,
_lock: threading.Lock = <factory>
) -> None
| method | what it does |
|---|---|
accepts(proposal: str) -> Tuple[bool, str] |
Would to_diff take this proposal? (accepted, reason). |
archive_rule(...)¶
Write an adopted slot rule back to the archive.
archive_rule(
store: ConsolidationStore,
report: MetaRunReport,
result: EvolutionResult,
*,
run_id: str,
slot: str,
spec: SlotSpec,
config: Optional[Dict[str, Any]] = None,
problem: Optional[Dict[str, Any]] = None,
artifact_id: str = 'meta-rule',
trajectory_store: Optional[str] = None
) -> ConsolidationRecord
auc(outcome: MetaOutcome) -> float¶
Mean best-so-far held-out reward over the inner run: how fast it rose.
compile_policy_source(...)¶
Gate source, instantiate its class_name, and check it fits slot.
compile_policy_source(
slot: str,
source: str,
*,
class_name: str = 'Policy',
smoke: Optional[Callable[[Any], None]] = None,
rng_seed: Optional[int] = None
) -> Any
compile_priority(source: str) -> Callable[..., float]¶
AST-gate source and return its priority function.
continuous_meta_run(...)¶
One session of continuous meta-learning: evolve, judge, archive.
continuous_meta_run(
store: ConsolidationStore,
slot: str,
spec: SlotSpec,
propose: Optional[Callable[[str, Task, str, float], Optional[str]]] = None,
*,
tasks_for: Callable[[ConsolidationRecord], Sequence[Task]],
run: Callable[[str, Task], str],
reward: Callable[[Task, str], float],
inner_propose: Callable[[str, Task, str, float], Optional[str]],
inner_strategy: Any,
config: Optional[Dict[str, Any]] = None,
problems: Optional[Union[Sequence[Problem], Mapping[str, Problem]]] = None,
val_problems: Optional[Union[Sequence[Problem], Mapping[str, Problem]]] = None,
artifact_id: str = 'meta-rule',
train_runs: int = 2,
val_runs: int = 1,
inner: Optional[Dict[str, Any]] = None,
model: Optional[Completion] = None,
meta_reward: Optional[MetaReward] = None,
seeds: Sequence[int] = (0,),
val_seeds: Sequence[int] = (1, 2),
min_gain: float = 0.0,
transfer_floor: float = 0.0,
run_id: Optional[str] = None,
min_exam: Optional[str] = None,
**evolve_kwargs: Any
) -> Tuple[ContinuousMetaReport, EvolutionResult]
| parameter | type | default | what it is |
|---|---|---|---|
store |
ConsolidationStore |
required | The ConsolidationStore that makes the loop continuous: rules it already holds seed this session, and an adopted rule is written back to it. |
slot |
str |
required | As meta_run -- which Policies field evolves, how a value is represented/gated/compiled, and the reflector (pass propose directly or model to get slot_reflector). |
spec |
SlotSpec |
required | As slot. |
propose |
Optional[Callable[[str, Task, str, float], Optional[str]]] |
None |
As slot. |
tasks_for |
Callable[[ConsolidationRecord], Sequence[Task]] |
required | Maps an archived ConsolidationRecord to the inner tasks of its problem -- the caller's data, read through the archive's configuration. |
run |
Callable[[str, Task], str] |
required | The inner evolve() domain functions, passed through to problem_from_archive for every rebuilt problem. |
reward |
Callable[[Task, str], float] |
required | As run. |
inner_propose |
Callable[[str, Task, str, float], Optional[str]] |
required | As run. |
inner_strategy |
Any |
required | As run. |
config |
Optional[Dict[str, Any]] |
None |
This session's configuration (dataset/scorer/model/params), used to rank archived rules and written back with the adopted rule. |
problems |
Optional[Union[Sequence[Problem], Mapping[str, Problem]]] |
None |
The fallback inner problems, used when the archive cannot fill a set (an empty archive is an ordinary meta_run). inner must describe how these were built for the archived descriptor to be faithful. |
val_problems |
Optional[Union[Sequence[Problem], Mapping[str, Problem]]] |
None |
As problems. |
artifact_id |
str |
'meta-rule' |
The archive artifact_id of rule records. |
train_runs |
int |
2 |
How many archived rules supply the train and validation problems. |
val_runs |
int |
1 |
As train_runs. |
inner |
Optional[Dict[str, Any]] |
None |
The JSON-safe inner evolve() settings (rounds, n_workers, target_reward ...): defaults for rebuilt problems and the descriptor archived with an adopted rule. |
model |
Optional[Completion] |
None |
As meta_run. |
meta_reward |
Optional[MetaReward] |
None |
As model. |
seeds |
Sequence[int] |
(0,) |
As model. |
val_seeds |
Sequence[int] |
(1, 2) |
As model. |
min_gain |
float |
0.0 |
As model. |
transfer_floor |
float |
0.0 |
As model. |
run_id |
Optional[str] |
None |
The archived run_id for the adopted rule; a unique meta-<slot>-<unix time>-<random> id when omitted (pass one explicitly when you need to find it again). |
min_exam |
Optional[str] |
None |
The seeding bar applied to archived rules before they seed this session, via verified_rules: None (the default) keeps today's behaviour, "flagged" accepts flagged and passed rules, and "passed" seeds only certified rules. Any other value raises. |
**evolve_kwargs |
Any |
Everything else meta_evolve takes, except initial_state (this function sets it from the archive) and strategy/run/ reward (the inner domain's, passed above). Returns (report, result); report.value is the evolved value and report.verdict the adopt/reject decision. |
evolve_problem(...)¶
An inner evolve() as a Problem.
evolve_problem(
tasks: Sequence[Task],
reward: Callable[[Task, str], float],
*,
slot: str,
base: Optional[Policies] = None,
**evolve_kwargs: Any
) -> Problem
final_reward(outcome: MetaOutcome) -> float¶
The inner run's own final held-out reward, clipped to [0, 1].
meta_evolve(...)¶
Evolve one decision slot of the engine against a set of inner problems.
meta_evolve(
problems: Union[Sequence[Problem], Mapping[str, Problem]],
*,
slot: str,
spec: SlotSpec,
propose: Optional[Callable[[str, Task, str, float], Optional[str]]] = None,
model: Optional[Completion] = None,
meta_reward: Optional[MetaReward] = None,
seeds: Sequence[int] = (0,),
blast_radius: float = 0.6,
artifact_id: str = 'policy-slot',
**evolve_kwargs: Any
) -> EvolutionResult
| parameter | type | default | what it is |
|---|---|---|---|
problems |
Union[Sequence[Problem], Mapping[str, Problem]] |
required | The inner problems, each (value, seed) -> MetaOutcome -- a list, or a mapping from a name to a problem (the name appears in task ids and in the reflector's prompt). evolve_problem builds one from the arguments of an inner evolve(). |
slot |
str |
required | Which Policies field the value fills; one of SLOTS. Recorded, and checked -- machinery fields refuse. |
spec |
SlotSpec |
required | How a value is represented, gated and compiled -- a SlotSpec such as priority_selection or a ParamSlot. |
propose |
Optional[Callable[[str, Task, str, float], Optional[str]]] |
None |
The reflector. Pass propose directly, or model to get slot_reflector over the spec. One of the two is required. |
model |
Optional[Completion] |
None |
As propose. |
meta_reward |
Optional[MetaReward] |
None |
MetaOutcome to [0, 1]; None is auc. |
seeds |
Sequence[int] |
(0,) |
Inner seeds per problem; each (problem, seed) pair is one outer task, so len(problems) * len(seeds) tasks in all, split into train and held-out by held_out_frac as evolve() always does. |
blast_radius |
float |
0.6 |
Governance. 0.6 is L1: the value is a harness and every merge also passes the oracle. |
artifact_id |
str |
'policy-slot' |
As blast_radius. |
**evolve_kwargs |
Any |
Everything else evolve takes -- rounds, n_workers, max_concurrency, held_out_frac, max_rollouts ... strategy, run and reward are this function's and cannot be passed. Returns the ordinary EvolutionResult; spec.compile(result.rendered) is the evolved value, and result.rendered is what to hand meta_validate. |
meta_run(...)¶
Evolve one decision slot, validate it on unseen problems, and decide.
meta_run(
problems: Union[Sequence[Problem], Mapping[str, Problem]],
val_problems: Union[Sequence[Problem], Mapping[str, Problem]],
*,
slot: str,
spec: SlotSpec,
propose: Optional[Callable[[str, Task, str, float], Optional[str]]] = None,
model: Optional[Completion] = None,
meta_reward: Optional[MetaReward] = None,
seeds: Sequence[int] = (0,),
val_seeds: Sequence[int] = (1, 2),
min_gain: float = 0.0,
transfer_floor: float = 0.0,
baseline: Optional[str] = None,
**evolve_kwargs: Any
) -> Tuple[MetaRunReport, EvolutionResult]
meta_validate(...)¶
Score before and after on problems the outer loop never saw.
meta_validate(
spec: SlotSpec,
before: str,
after: str,
problems: Union[Sequence[Problem], Mapping[str, Problem]],
*,
seeds: Sequence[int] = (0,),
meta_reward: Optional[MetaReward] = None
) -> Dict[str, Dict[str, float]]
policy_source(...)¶
The general spec: slot's value is the source of a class satisfying its Protocol.
policy_source(
slot: str,
seed: Optional[str] = None,
*,
class_name: str = 'Policy',
smoke: Optional[Callable[[Any], None]] = None,
notes: str = ''
) -> SourceSlot
priority_selection(...)¶
The shipped spec for the selection slot of a tree search.
priority_selection(
seed: str = 'def priority(rank, visits, total, prior, depth, n_nodes):\n # Flat PUCT (ERA, futs.py): exploit by rank, explore by visit count.\n c = 1.0\n return rank + c * (1.0 / n_nodes) * math.sqrt(total) / (1 + visits)\n'
) -> SourceSlot
problem_descriptor(slot: str, **inner: Any) -> Dict[str, Any]¶
The serializable shape of one inner Problem, for an archive.
problem_from_archive(...)¶
Rebuild an inner Problem from an archived rule's descriptor.
problem_from_archive(
store: ConsolidationStore,
run_id: str,
*,
tasks_for: Callable[[ConsolidationRecord], Sequence[Task]],
spec: SlotSpec,
run: Callable[[str, Task], str],
reward: Callable[[Task, str], float],
propose: Callable[[str, Task, str, float], Optional[str]],
strategy: Any,
**defaults: Any
) -> Problem
| parameter | type | default | what it is |
|---|---|---|---|
store |
ConsolidationStore |
required | The archive holding the record. |
run_id |
str |
required | Which archived run to rebuild from. KeyError when it is not archived or carries no problem descriptor. |
tasks_for |
Callable[[ConsolidationRecord], Sequence[Task]] |
required | Maps an archived ConsolidationRecord to the inner tasks its problem ran on. The caller owns the data; the archive owns the semantics and the configuration. |
spec |
SlotSpec |
required | The outer SlotSpec -- used as a guard, not as an input to the inner run: the record's strategy must be this spec's class name, the same "adopt through the strategy that wrote the state" rule seed_from_archive enforces. A record archived by a different spec is refused, never silently misread. |
run |
Callable[[str, Task], str] |
required | The inner problem's own evolve() parts -- the domain functions. |
reward |
Callable[[Task, str], float] |
required | As run. |
propose |
Callable[[str, Task, str, float], Optional[str]] |
required | As run. |
strategy |
Any |
required | As run. |
**defaults |
Any |
Inner evolve() settings. Each overrides the archived value: the descriptor says how the problem was run then; the caller may want it run differently now. |
rollouts_to(target: float) -> MetaReward¶
1 / (1 + sweeps until the curve first reaches target); 0 if never.
seed_source(slot: str) -> str¶
A valid starting value for slot, as candidate source.
slot_reflector(...)¶
A propose for meta_evolve: one model call per failing rollout.
slot_reflector(
complete: Completion,
spec: SlotSpec,
*,
max_outcome_chars: int = 2000
) -> Callable[[str, Task, str, float], Optional[str]]
transfer_ratio(...)¶
Gain on target over gain on source, from a meta_validate report.
transfer_ratio(
report: Mapping[str, Mapping[str, float]],
source: str,
target: str
) -> Optional[float]
Learn-to-think¶
Evolve when the framework spends compute: direct, search, or reflect. · agentdescent.learn_to_think · guide
LearnToThinkReport(...)¶
What learn_to_think found: the evolved route rule and how its quality-per-cost compares with the default on validation problems.
LearnToThinkReport(
seed_value: str,
value: str,
seed_score: float,
evolved_score: float,
gain: float,
routes_on: Dict[str, str]
) -> None
learn_to_think(...)¶
Evolve the route rule on quality-per-cost.
learn_to_think(
problems: Union[Sequence[Problem], Mapping[str, Problem]],
val_problems: Union[Sequence[Problem], Mapping[str, Problem]],
*,
propose: Optional[Callable[[str, Task, str, float], Optional[str]]] = None,
model: Optional[Any] = None,
seeds: Sequence[int] = (0, 1, 2, 3),
val_seeds: Sequence[int] = (4, 5, 6, 7),
seed_source: str = 'class Policy:\n # Answer directly unless the task is hard and the budget allows a search.\n def route(self, difficulty, budget_left, reward):\n if difficulty >= 0.7 and budget_left >= 0.5:\n return "search"\n if difficulty >= 0.4:\n return "reflect"\n return "direct"\n',
**evolve_kwargs: Any
) -> Tuple[LearnToThinkReport, EvolutionResult]
quality_per_cost(outcome: MetaOutcome) -> float¶
The outcome's reward, read back (already quality-per-cost).
think_policy_spec(...)¶
The evolvable spec: the value is a Policy.route source, gated and compiled by _build_route (a smoke test runs the route on an easy, a hard-with-budget and a hard-without-budget input).
think_policy_spec(
seed: str = 'class Policy:\n # Answer directly unless the task is hard and the budget allows a search.\n def route(self, difficulty, budget_left, reward):\n if difficulty >= 0.7 and budget_left >= 0.5:\n return "search"\n if difficulty >= 0.4:\n return "reflect"\n return "direct"\n'
) -> SourceSlot
think_problem(...)¶
A Problem over one workload instance.
think_problem(
*,
lam: float = 0.1,
base_quality: Optional[Callable[[float], float]] = None
) -> Problem
Agents and models¶
Any prompt -> text is a completion; a WorkspaceAgent also has a directory. · agentdescent.agents · guide
AgentError¶
A tool-using agent failed; the message carries its stderr / exit status.
Usage(...)¶
What a run cost: calls, tokens, and wall-clock spent in the model.
Usage(
calls: int = 0,
prompt_tokens: int = 0,
completion_tokens: int = 0,
seconds: float = 0.0,
failures: int = 0,
failure_seconds: float = 0.0,
on_change: Optional[Callable[[], None]] = None,
_lock: threading.Lock = <factory>
) -> None
| method | what it does |
|---|---|
add_tokens(prompt_tokens: int = 0, completion_tokens: int = 0) -> None |
Add provider-reported tokens without counting a call or any time. |
begin(phase: Optional[str] = None) -> int |
Mark an actor invocation as started; returns its token. |
end(token: int) -> None |
Finish the invocation begin returned token for. |
estimated_cost(per_1m_prompt: float, per_1m_completion: float) -> float |
Cost at the given per-million-token prices (both provider-specific). |
record(...) |
Account one finished call. finishes is the begin token of the invocation this record completes; without it, in-flight state is left alone (an unpaired provider attempt is not the actor call). |
token_sink() -> 'TokenSink' |
A usage= for a model adapter that adds only its tokens here. |
WorkspaceAgent¶
A Completion that can additionally be bound to a directory.
anthropic_compatible(...)¶
A completion for any Anthropic-format endpoint, with no SDK dependency.
anthropic_compatible(
model: str,
*,
base_url_env: str = 'ANTHROPIC_BASE_URL',
api_key_env: str = 'ANTHROPIC_API_KEY',
default_base_url: str = 'https://api.anthropic.com',
version: str = '2023-06-01',
max_tokens: int = 4096,
timeout: float = 120.0,
usage: Optional[Usage] = None,
retries: int = 3,
**create_kwargs
) -> Completion
claude(...)¶
A Claude-backed completion (requires pip install anthropic + creds).
claude(
model: str = 'claude-opus-4-8',
max_tokens: int = 4096,
client: Optional[object] = None,
usage: Optional[Usage] = None,
retries: int = 3,
timeout: float = 120.0,
api_key_env: Optional[str] = None,
**create_kwargs
) -> Completion
claude_code(...)¶
Claude Code in non-interactive print mode, as a Completion.
claude_code(
*,
workspace: Optional[str] = None,
extra_args: Sequence[str] = (),
**kwargs
) -> Completion
cli_agent(...)¶
Run any command-line coding agent as a Completion.
cli_agent(
command: Sequence[str],
*,
workspace: Optional[str] = None,
via_stdin: bool = False,
timeout: float = 600.0,
env: Optional[Dict[str, str]] = None,
usage: Optional[Usage] = None,
isolate: bool = True
) -> 'WorkspaceAgent'
codex(...)¶
OpenAI Codex CLI in non-interactive exec mode, as a Completion.
dsh(*, workspace: Optional[str] = None, extra_args: Sequence[str] = (), **kwargs) -> Completion¶
DeepSeek Harness (dsh) headless profile, as a Completion.
echo(transform: Optional[Callable[[str], str]] = None) -> Completion¶
A deterministic, no-network completion for tests and dry runs.
from_callable(fn: Completion) -> Completion¶
Identity adapter -- documents that any prompt -> text callable works.
metered(completion: Completion, usage: Usage) -> Completion¶
Count calls and model wall-clock for any completion.
openai_compatible(...)¶
A completion for any OpenAI-compatible chat endpoint (GLM/Zhipu, proxies, local servers, OpenAI itself).
openai_compatible(
model: str,
*,
base_url_env: str = 'OPENAI_BASE_URL',
api_key_env: str = 'OPENAI_API_KEY',
default_base_url: str = 'https://api.openai.com/v1',
max_tokens: int = 4096,
timeout: float = 120.0,
usage: Optional[Usage] = None,
retries: int = 3,
stream: bool = False,
**create_kwargs
) -> Completion
opencode(...)¶
OpenCode's non-interactive run mode, as a Completion.
opencode(
*,
workspace: Optional[str] = None,
extra_args: Sequence[str] = (),
**kwargs
) -> Completion
with_retries(...)¶
Wrap a completion with exponential-backoff retries on any exception.
with_retries(
completion: Completion,
attempts: int = 3,
backoff: float = 0.5,
sleep: Callable[[float], None] = <built-in function sleep>,
rate_limit_backoff: float = 5.0,
max_sleep: float = 60.0
) -> Completion
worker_env(...)¶
The environment a worker agent CLI runs with.
worker_env(
workspace: Optional[str],
extra: Optional[Mapping[str, str]] = None,
*,
isolate: bool = True
) -> Dict[str, str]
Directories as state¶
Load a directory into state, materialise it back, serialise it losslessly. · agentdescent.filetree · guide
TreeError¶
A directory could not be represented as evolvable state, or vice versa.
TreeSpec(...)¶
Which files make up an evolvable tree, and how big it may get.
TreeSpec(
include: Sequence[str] = ('**/*.md', '**/*.txt', '**/*.py', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.toml', '**/*.sh', '**/*.cfg', '**/*.ini'),
exclude: Sequence[str] = ('**/.git/**', '**/__pycache__/**', '**/node_modules/**', '**/.venv/**', '**/*.egg-info/**', '**/.pytest_cache/**', '**/.DS_Store'),
max_file_bytes: int = 28000,
max_files: int = 200,
max_total_bytes: int = 2000000
) -> None
| method | what it does |
|---|---|
validate_against(trust_region_chars: int) -> None |
Fail now if the loader admits files the optimizer can never accept. |
canonical(state: Mapping[str, str]) -> str¶
A lossless, stable serialisation of a file tree.
load_tree(path: str, spec: Optional[TreeSpec] = None) -> Dict[str, str]¶
Read a directory into {relpath: text}.
materialize(...)¶
Write a tree into dest (optionally under prefix); return the paths.
materialize(
state: Mapping[str, str],
dest: str,
*,
prefix: str = '',
exec_patterns: Sequence[str] = ('**/*.sh', 'scripts/**', '**/bin/**')
) -> List[str]
parse_tree(rendered: str) -> Dict[str, str]¶
The inverse of canonical.
tree_summary(state: Mapping[str, str], limit: int = 40) -> str¶
A human/LLM-readable listing (paths + sizes), for prompts and logs.
The file-tree strategy¶
One state key per file, plus the multi-file proposal protocol. · agentdescent.treestrategy · guide
FileTree(...)¶
The artifact is a directory; each state key is a relative file path.
FileTree(
initial_files: Mapping[str, str] = <factory>,
editable: Sequence[str] = ('**',),
frozen: Sequence[str] = (),
max_files_per_diff: int = 2,
max_file_bytes: int = 28000,
planned_paths: Sequence[str] = (),
validators: Sequence[Callable[[Mapping[str, str]], Optional[str]]] = ()
) -> None
| method | what it does |
|---|---|
check_candidate(state: Mapping[str, str], ops: Mapping[str, Optional[str]]) -> Optional[str] |
Run validators on state with ops applied. |
frozen_files(source: Mapping[str, str]) -> Dict[str, str] |
The pristine content of every frozen path, for the runner's overlay. |
keys() -> Sequence[str] |
The declared key space, for TensorParallel. |
writable(path: str) -> bool |
May the loop write this path? frozen beats editable. |
parse_edits(proposal: str) -> Dict[str, Optional[str]]¶
Parse a reflector reply into {path: new_content} (None = delete).
parse_proposal(proposal: str) -> Tuple[Dict[str, Optional[str]], str]¶
Parse a reflector reply into (edits, rationale).
tree_reflector(...)¶
A propose callable that asks complete for multi-file edits.
tree_reflector(
complete: Completion,
*,
strategy: 'FileTree',
context_files: Sequence[str] = ('**/SKILL.md', '**/AGENT.md', '*.md'),
max_context_chars: int = 12000,
max_output_chars: int = 2000,
template: str = 'You maintain the files below. An agent used them to do a task and did poorly (reward {reward:.2f} out of 1.00). Improve the files so this class of failure stops happening -- generalise, do not hard-code this one case.\n\nFILES IN THE ARTIFACT:\n{listing}\n\n{contents}\nTASK THE AGENT WAS GIVEN:\n{prompt}\n\nWHAT THE AGENT PRODUCED:\n{output}\n{expected}\n{protocol}'
) -> Any
Runners¶
Give a real agent the candidate directory, one workspace per rollout. · agentdescent.runners · guide
PluginHost(...)¶
How one host loads an uninstalled plugin from a path, as data.
PluginHost(
name: str,
entrypoint: Sequence[str],
setup: Optional[Sequence[str]] = None,
validate: Optional[Sequence[str]] = None,
env: Mapping[str, str] = <factory>
) -> None
code_runner(...)¶
Run candidate code on a task: materialise, gate, execute.
code_runner(
entrypoint: Sequence[str],
*,
layout: str = 'root',
name: str = 'agent',
setup_cmd: Optional[Sequence[str]] = None,
test_cmd: Optional[Sequence[str]] = None,
overlay: Optional[Mapping[str, str]] = None,
fixtures: Optional[Callable[[Task], Mapping[str, str]]] = None,
timeout: float = 120.0,
env: Optional[Mapping[str, str]] = None,
workspace_root: Optional[str] = None,
sandbox_pool: Optional['SandboxPool'] = None
) -> Callable[[str, Task], str]
gated_reward(reward: Callable[[Task, str], float]) -> Callable[[Task, str], float]¶
reward, with a failed code_runner gate scoring 0.
plugin_runner(...)¶
Run a host plugin on a task: materialise it, load it into an isolated copy of the host, gate it, then run the host on the prompt.
plugin_runner(
host: Union[str, PluginHost],
*,
name: str = 'plugin',
agent_args: Sequence[str] = (),
env_passthrough: Sequence[str] = (),
overlay: Optional[Mapping[str, str]] = None,
fixtures: Optional[Callable[[Task], Mapping[str, str]]] = None,
timeout: float = 900.0,
workspace_root: Optional[str] = None,
sandbox_pool: Optional['SandboxPool'] = None
) -> Callable[[str, Task], str]
tree_runner(...)¶
Build a run(rendered, task) that gives agent the evolving directory.
tree_runner(
agent: Completion,
*,
layout: str = 'claude_skill',
name: str = 'artifact',
prompt_template: str = '{prompt}\n\n(The files under {tree_dir} in this directory are available to you; read them and follow them. Reply with only the final answer.)',
overlay: Optional[Mapping[str, str]] = None,
fixtures: Optional[Callable[[Task], Mapping[str, str]]] = None,
answer_file: Optional[str] = None,
keep_failed: bool = False,
workspace_root: Optional[str] = None,
sandbox_pool: Optional['SandboxPool'] = None
) -> Callable[[str, Task], str]
The data model¶
What a unit of evolution is, and what a gradient looks like here. · agentdescent.evolvable · guide
Contract(...)¶
The externally-visible interface of an artifact.
Contract(
input_schema: str = 'any',
output_schema: str = 'any',
side_effects: Tuple[str, ...] = (),
major: int = 1,
invariants: Tuple[Any, ...] = (),
depends_on: Tuple[str, ...] = ()
) -> None
ContractError¶
The caller's own code broke a documented contract.
Diff(...)¶
A proposed change to an artifact's state.
Diff(
diff_id: str,
target: str,
ops: Dict[str, Any] = <factory>,
contract_breaking: bool = False,
author: str = 'unknown',
rationale: str = ''
) -> None
| method | what it does |
|---|---|
size() -> int |
A crude "number of edited lines" proxy used by the trust-region cap (design doc, section 4.4). |
EvidenceCard(...)¶
The "gradient metadata" carried by every diff (design doc, section 3.3).
EvidenceCard(
diff: Diff,
base_version: VersionVector,
touched: List[str],
before_after_delta: float = 0.0,
trajectory_refs: List[Any] = <factory>,
trace_ids: Tuple[str, ...] = <factory>,
advantage: Optional[float] = None,
cost_tokens: int = 0,
cost_wallclock: float = 0.0,
branch: Optional[str] = None
) -> None
| method | what it does |
|---|---|
rebased_onto(head: VersionVector) -> 'EvidenceCard' |
Return a copy whose base is advanced to head for touched keys. |
Evolvable¶
The single interface every unit of evolution must satisfy.
stable_hash(key: Any) -> int¶
A process-independent hash for seeding and partitioning.
vv_dominates(a: VersionVector, b: VersionVector) -> bool¶
Return True if a is at least as new as b on every shared key.
vv_staleness(head: VersionVector, base: VersionVector) -> int¶
Per-diff staleness eta (design doc, section 4.2).
The aggregator (the optimizer)¶
Staleness filter, conflict resolution, fusion, acceptance, commit. · agentdescent.aggregator · guide
Aggregator(...)¶
Per-artifact optimizer step over the ledger.
Aggregator(
ledger: Ledger,
verifier: ThreeLayerVerifier,
audit: AuditScheduler,
config: Optional[AggregatorConfig] = None,
staleness_policy: Optional[StalenessPolicy] = None,
meter: Optional['Meter'] = None,
conflict: Optional['ConflictPolicy'] = None,
fusion: Optional['FusionPolicy'] = None,
acceptance: Optional['AcceptancePolicy'] = None,
promotion: Optional['PromotionPolicy'] = None,
screen: Optional['ScreenPolicy'] = None
) -> None
| method | what it does |
|---|---|
begin_step(*, skip_in_flight: bool = False) -> List[Union['_Candidate', MergeReport]] |
Phases 1 and 2: tick, drain what is ready, choose candidates. |
checkpoint() -> Optional[dict] |
Serialise the search state the ledger does not persist. |
finalize() -> None |
Publish the current dev head to stable at the end of a clean run. |
finish_step(items: List[Union['_Candidate', MergeReport]]) -> List[MergeReport] |
Phase 3: decide the measured candidates, then age and promote. |
head_for_worker(worker_id: int = 0) -> str |
The ledger branch a worker should snapshot and propose against. |
measure(items: List[Union['_Candidate', MergeReport]]) -> List[Union['_Candidate', MergeReport]] |
Phase 2 for a batch from begin_step. Off-thread safe. |
recent_settled(task: Any, n: int = 3) -> List[EvidenceCard] |
The most recent N settled cards whose trajectory_refs mention task. |
restore(state: dict) -> None |
Restore state written by checkpoint. |
set_settled_consumer(consumer: Optional[Callable[[EvidenceCard], None]]) -> None |
Subscribe a consumer to the settled-evidence pool. |
step(n_workers: int = 1) -> List[MergeReport] |
Fire every artifact bucket that is ready and return per-artifact reports. |
AggregatorConfig(...)¶
AggregatorConfig(
batch_trigger: int = 4,
max_wait_rounds: int = 3,
base_delta: float = 0.5,
alpha_head: int = 5,
alpha_tail: int = 1,
trust_region_ops: int = 6,
trust_region_chars: int = 32000,
trust_region_policy: Optional[Any] = None,
promote_after_k: int = 3,
anneal_half_life: int = 64,
accept_samples: int = 4000,
cas_attempts: int = 3,
cas_backoff: float = 0.05,
audit_drain_per_step: int = 0,
fusion_tournament: bool = False,
bounded_gate: bool = False
) -> None
AggregatorContractError¶
A custom aggregator returned something step() may not return.
AggregatorProtocol¶
The contract a custom aggregator must satisfy to plug into evolve.
EvidenceBuffer() -> None¶
Cards bucketed by (target artifact, branch) (design doc, section 4.1).
| method | what it does |
|---|---|
drain(aid: str) -> Dict[str, List[EvidenceCard]] |
Pop every bucket for aid, grouped by branch ("" = dev). |
ready(config: AggregatorConfig) -> List[str] |
Artifact ids with a ready bucket. Each id is listed once, whatever branches it has: the caller drains all of them and merges per branch. |
recent_settled(task: Any, n: int = 3) -> List[EvidenceCard] |
The most recent N settled cards whose trajectory_refs mention task. |
set_settled_consumer(consumer: Optional[Callable[[EvidenceCard], None]]) -> None |
Subscribe a consumer to every card that is settled. |
settle(cards: List[EvidenceCard]) -> None |
Keep discarded-diff evidence addressable, under a hard bound. |
MergeOutcome¶
The vocabulary of category.
| member | value |
|---|---|
COMMITTED |
'committed' |
BELOW_THRESHOLD |
'below-threshold' |
ALL_STALE |
'all-stale' |
OVERSIZED |
'oversized' |
ORACLE_REJECTED |
'oracle-rejected' |
CAS_CONFLICT |
'cas-conflict' |
UNKNOWN_ARTIFACT |
'unknown-artifact' |
VIOLATES_INVARIANT |
'violates-invariant' |
MISSING_ADAPTERS |
'missing-adapters' |
CRITIC_REJECTED |
'critic-rejected' |
NEEDS_APPROVAL |
'needs-approval' |
APPROVAL_REJECTED |
'approval-rejected' |
NOISE_FLOOR |
'noise-floor' |
COST_RULE |
'cost-rule' |
MergeReport(...)¶
MergeReport(
artifact_id: str,
accepted: Optional[Diff],
fused: bool,
considered: int,
survived_staleness: int,
discarded_stale: int,
conflicts_dropped: int,
prob_improve: float,
committed_version: Optional[int],
reason: str = '',
category: str = '',
oversized: int = 0,
candidate_diff: Optional[Diff] = None
) -> None
diffs_conflict(a: Diff, b: Diff) -> bool¶
Syntactic overlap: do two diffs edit an overlapping set of keys?
diffs_contradict(a: Diff, b: Diff) -> bool¶
Semantic contradiction: same key, different proposed value.
fuse_diffs(diffs: List[Diff]) -> Diff¶
Merge complementary (non-contradicting) diffs into one candidate.
The ledger¶
The git-backed, compare-and-swap artifact store. · agentdescent.ledger · guide
CASConflict¶
Raised when a commit's declared base version is stale.
ContractRejected¶
Raised when a commit would change an artifact's contract major.
GitError¶
A git command failed; the message carries git's own stderr.
Ledger(...)¶
A git-backed, version-vectored artifact store with dual branches.
Ledger(
repo_path: str,
serialize: Serializer,
deserialize: Deserializer,
author: str = 'agentdescent <bot@agentdescent.local>'
) -> None
| method | what it does |
|---|---|
close() -> None |
Refuse further use of this ledger. Idempotent. |
commit(...) |
Compare-and-swap commit of a single artifact. |
commit_atomic(...) |
Two-phase, all-or-nothing commit of several artifacts. |
discard_head(name: str) -> None |
Delete a forked head branch. |
fork(name: str, from_branch: str = 'dev') -> str |
Create or reset name to hold from_branch's current state. |
live_heads() -> List[str] |
Every live head branch: dev first, then the head/ forks. |
promote_to_stable(artifact_id: str) -> Optional[int] |
EMA-style confirmation: copy dev's current artifact onto stable. |
register(artifact: Evolvable, branch: str = 'dev') -> None |
Add a brand-new artifact at version 1 on both branches. |
snapshot(branch: str = 'dev') -> Snapshot |
Materialize every artifact on branch into live Evolvables. |
Snapshot(artifacts: Dict[str, Evolvable], version: VersionVector) -> None¶
An immutable view of one branch at one point in time.
The verifier¶
Rule / learned / oracle, and the budget that bounds the expensive one. · agentdescent.verifier · guide
ThreeLayerVerifier(...)¶
Rule / learned / full backend for the aggregator.
ThreeLayerVerifier(
eval_fn: EvalFn,
held_out: Sequence,
rule_subset: int = 8,
learned_noise: float = 0.04,
seed: int = 0,
budget: VerifierBudget = <factory>
) -> None
| method | what it does |
|---|---|
cheap_eval(artifact: Evolvable) -> float |
The signal used everywhere a budget-free score is needed. |
eval_counts(artifact: Evolvable, floor: Optional[float] = None) -> Tuple[float, float] |
Return (successes, failures) on the full held-out set. |
full_eval(artifact: Evolvable) -> float |
eval_fn on the whole held-out set. Consumes audit budget. |
learned_eval(artifact: Evolvable) -> Tuple[float, float] |
Noisy proxy that also returns an uncertainty estimate. |
oracle_eval(artifact: Evolvable) -> float |
Deprecated alias for full_eval. Removed in 0.7. |
rule_eval(artifact: Evolvable) -> float |
Cheap, deterministic-ish check on a tiny subset. |
VerifierBudget(oracle_calls_remaining: int = 200, oracle_calls_used: int = 0) -> None¶
Budget for full-set evaluations, consumed by full_eval.
The sparse audit layer¶
Pair a cheap verifier against ground truth without paying for truth. · agentdescent.audit.tap · guide
AuditedReward(...)¶
A cheap verifier that hands a sampled minority of its work to the truth.
AuditedReward(
verifier: Callable[[Any, str], float],
*,
oracle: Optional[Any] = None,
store: Optional[AuditStore] = None,
draw_by: str = 'task',
sample_rate: float = 0.1,
stratify: Optional[Callable[[Any, str, float], str]] = None,
rates: Optional[Dict[str, float]] = None,
calibration_fraction: float = 0.7,
seed: int = 0,
verifier_version: Optional[str] = None,
version_extra: Any = None,
correction_extra: Any = None
) -> None
| parameter | type | default | what it is |
|---|---|---|---|
verifier |
Callable[[Any, str], float] |
required | The cheap scorer the loop optimises against -- an agent judging the output, a learned scorer, a heuristic. (task, output) -> float. |
oracle |
Optional[Any] |
None |
Ground truth. GoldAnswer when it returns now, DeferredOracle when it arrives later. Defaults to NullOracle, which still records the questions -- useful when the answerer has not been asked yet. |
store |
Optional[AuditStore] |
None |
Where records go. Defaults to an in-memory store; pass AuditStore("audit.jsonl") to keep them. |
draw_by |
str |
'task' |
What the inclusion draw is a function of. "task" (the default) audits a task whole or not at all; "output" draws per unit. They are identical when each task is scored once, and differ exactly where the difference matters. A run scores the same task again for every artifact version, and a per-unit draw then puts that task in both the labelled and the unlabelled half -- which the estimator assumes cannot happen. Measured at a nominal 0.95, on 200 tasks scored under four versions each: coverage 0.9125 when the halves share tasks and 0.945 when they do not. "output" buys more distinct tasks per label and an interval about 15% too narrow. Use it only when a task is scored once, where it is the same thing. |
sample_rate |
float |
0.1 |
Probability a unit is audited, when no stratum-specific rate applies. |
stratify |
Optional[Callable[[Any, str, float], str]] |
None |
Optional (task, output, verifier_score) -> str. Names the layer a unit belongs to, so rates can spend more of the budget where the residual varies most. The stratum is recorded either way. |
rates |
Optional[Dict[str, float]] |
None |
Per-stratum inclusion probabilities, falling back to sample_rate. |
calibration_fraction |
float |
0.7 |
Share of audited units assigned CALIBRATION; the rest become IMPROVEMENT. The split is drawn at random rather than taken in order, because units arrive grouped by task and by artifact and any ordered split would correlate the two pools with whatever the ordering happens to encode. |
seed |
int |
0 |
Base seed for the inclusion draw. |
verifier_version |
Optional[str] |
None |
Overrides the fingerprint derived from verifier. Pass one when the verifier is an agent whose behaviour lives in a prompt or a model id rather than in the source of the function. |
version_extra |
Any |
None |
Mixed into the derived fingerprint. The cheaper way to say the same thing: version_extra={"model": "...", "prompt_sha": "..."}. |
correction_extra |
Any |
None |
A live tag (a callable, e.g. VerifierBoost.version_tag) for a correction layered on the scorer. It is mixed into verifier_version -- so the audit re-measures when the correction changes -- but not into scorer_version, which keeps naming the scorer alone. |
| method | what it does |
|---|---|
calibration_set() -> list |
Resolved CALIBRATION records for this verifier version. |
pending() -> list |
Audited units still waiting on truth. |
refresh_verifier_version() -> str |
Re-derive verifier_version from a live version_extra. |
RenderTap(run: Callable[[str, Any], str]) -> None¶
Optional wrapper for run that records which artifact produced an output.
Switching the audit on¶
One call that assembles the six pieces and the four cross-references. · agentdescent.audit.wiring · guide
Audit(...)¶
The assembled layer. Hand the three fields to evolve().
Audit(
reward: AuditedReward,
run: Optional[RenderTap],
acceptance: RectifiedAcceptance,
store: AuditStore,
calibrator: Calibrator,
watch: VerifierWatch,
boost: Optional['VerifierBoost'] = None
) -> None
| method | what it does |
|---|---|
rebalance(unseen: float, **kw: Any) -> float |
Move the calibration share as the improvement pool stops learning. |
recompute() -> Rectification |
Re-read the store and re-estimate. Call after a batch resolves. |
rectification() -> Rectification |
The correction in force. Never raises; stale is an answer. |
status() -> Dict[str, Any] |
What the audit knows, for a round hook or a log line. |
attach(...)¶
Assemble the audit around verifier and return what evolve() needs.
attach(
verifier: Callable[[Any, str], float],
*,
oracle: Optional[Any] = None,
store: Union[AuditStore, str, None] = None,
run: Optional[Callable[[str, Any], str]] = None,
inner: Any = None,
enabled: bool = True,
plan: Optional[SamplePlan] = None,
policy: Optional[AuditPolicy] = None,
sample_rate: float = 0.1,
stratify: Optional[Callable[[Any, str, float], str]] = None,
calibration_fraction: float = 0.7,
draw_by: str = 'task',
seed: int = 0,
verifier_version: Optional[str] = None,
version_extra: Any = None,
watch_ids: Iterable[str] = (),
watch_globs: Sequence[str] = (),
boost: Optional['VerifierBoost'] = None
) -> Audit
| parameter | type | default | what it is |
|---|---|---|---|
verifier |
Callable[[Any, str], float] |
required | The cheap scorer the loop optimises against, (task, output) -> float. |
oracle |
Optional[Any] |
None |
Ground truth. A bare callable (task, output) -> float is wrapped in GoldAnswer, because that is what every caller with a gold answer already has and asking them to wrap it adds an import and a chance to forget that an oracle must never raise into the rollout. None records the questions without answering them. |
store |
Union[AuditStore, str, None] |
None |
An AuditStore, or a path to open one at. A path is the usual case: the process that resolves a deferred oracle is not this one. |
run |
Optional[Callable[[str, Any], str]] |
None |
The loop's run. Wrapped in a RenderTap so each unit records which artifact produced it. Leave it out and the audit still estimates the bias; it just cannot attribute a unit to an artifact. |
inner |
Any |
None |
|
enabled |
bool |
True |
False collects records and does not correct anything -- the gate delegates to inner on the untouched context. The honest way to run a first round: measure before you spend. |
plan |
Optional[SamplePlan] |
None |
A SamplePlan from a previous round, which carries per-stratum rates and overrides sample_rate. |
policy |
Optional[AuditPolicy] |
None |
|
sample_rate |
float |
0.1 |
|
stratify |
Optional[Callable[[Any, str, float], str]] |
None |
|
calibration_fraction |
float |
0.7 |
|
draw_by |
str |
'task' |
|
seed |
int |
0 |
|
verifier_version |
Optional[str] |
None |
|
version_extra |
Any |
None |
|
watch_ids |
Iterable[str] |
() |
Artifact ids and diff-key globs that mean the verifier changed. Nothing is watched by default, which is right for a fixed function and exactly wrong for a run that evolves its own judge -- see VerifierWatch. |
watch_globs |
Sequence[str] |
() |
As watch_ids. |
boost |
Optional['VerifierBoost'] |
None |
Audit oracle sources¶
Where ground truth comes from, and how long it takes to arrive. · agentdescent.audit.sources · guide
DeferredOracle(*, max_queued: Optional[int] = None) -> None¶
Truth that arrives later: an experiment, a human, an overnight job.
| method | what it does |
|---|---|
forget(record_id: str) -> None |
Drop a resolved id from the queue view. |
queued() -> List[str] |
Record ids awaiting an answer, oldest first. |
GoldAnswer(fn: Callable[[object, str], float]) -> None¶
Synchronous truth: a gold answer, an exact match, a checker, a simulator.
NullOracle()¶
An oracle that never answers. The default, and it is not a no-op.
OracleSource¶
Ground truth for one (task, output) pair.
resolve_from_mapping(store, answers: Dict[str, float], *, at: Optional[float] = None) -> int¶
Fill in truth for many pending records at once. Returns how many landed.
The audit store¶
Append-only persistence for paired observations, and the two pools. · agentdescent.audit.store · guide
AuditStore(path: Optional[str] = None) -> None¶
Records on disk, indexed in memory.
| method | what it does |
|---|---|
all() -> List[AuditRecord] |
Every record, in the order first seen. |
flush() -> None |
Persist the moments now. Call it when a run ends. |
for_calibration(verifier_version: str) -> List[AuditRecord] |
Resolved CALIBRATION records for one verifier version, and nothing else. |
for_improvement(...) |
Resolved IMPROVEMENT records -- the pool you are allowed to look at. |
load() -> None |
Re-read the file, last-occurrence-wins. |
observe_skipped(verifier_version: str, stratum: str) -> None |
Count one unit the tap dropped, without its score. |
observe_unlabelled(verifier_version: str, stratum: str, score: float) -> None |
Fold one un-audited score into its stratum's running moments. |
pending(...) |
Records still waiting on truth -- the work list for whoever answers. |
remember_fingerprint(fingerprint: str) -> None |
Persist the fingerprint so a restart can be compared against it. |
remember_priorities(priorities: Dict[str, float]) -> None |
Record what the merge path thought was worth auditing. |
reopen(record_id: str) -> bool |
Clear a resolution so it can be replaced. For corrections, not for retries. |
resolve(record_id: str, oracle_score: float, *, at: Optional[float] = None) -> bool |
Attach ground truth to a pending record. False if there was none to attach. |
unlabelled_moments(verifier_version: str) |
stratum -> {n, mean, var, skipped} over the units not audited. |
versions() -> List[str] |
Every verifier_version seen, in order of first appearance. |
summarise(records: Iterable[AuditRecord]) -> Dict[str, float]¶
Counts and the raw mean residual. Not an estimate of the bias.
Diagnosing the verifier¶
Sort the residual by what fixing it would cost, and measure a proposed fix. · agentdescent.audit.diagnose · guide
Direction¶
Which way the verifier was wrong.
| member | value |
|---|---|
OVER |
'over' |
UNDER |
'under' |
Disagreement(record: AuditRecord, kind: Kind, direction: Direction, note: str = '') -> None¶
DisagreementReport(...)¶
The residual, sorted by what fixing it would cost.
DisagreementReport(
n_pairs: int,
n_disagree: int,
delta: float,
sigma: float,
by_kind: Dict[Kind, int],
by_direction: Dict[Direction, int],
by_kind_direction: Dict[Tuple[Kind, Direction], int],
floor_sigma: float,
sigma_without: Dict[Kind, float],
items: List[Disagreement] = <factory>
) -> None
FixReport(...)¶
What a proposed change to the verifier actually costs.
FixReport(
n_pairs: int,
delta_before: float,
delta_after: float,
sigma_before: float,
sigma_after: float,
disagree_before: float,
disagree_after: float,
fixed: int,
broken: int,
breakage_rate: float,
false_negative_before: float,
false_negative_after: float,
unchanged: int,
moved: int = 0,
noise_floor: int = 0
) -> None
Kind¶
What it would take to fix this disagreement. Ordered by increasing cost.
| member | value |
|---|---|
FORMATTING |
'formatting' |
SPEC_GAP |
'spec_gap' |
AMBIGUOUS |
'ambiguous' |
JUDGMENT |
'judgment' |
UNCLASSIFIED |
'unclassified' |
classify_disagreements(...)¶
Sort a store's resolved disagreements by what fixing them would take.
classify_disagreements(
records: Iterable[AuditRecord],
classifier: Optional[Classifier] = None,
context: Optional[Mapping[str, Any]] = None
) -> DisagreementReport
evaluate_fix(...)¶
Score a proposed verifier change against every labelled pair.
evaluate_fix(
records: Iterable[AuditRecord],
fix: Callable[[AuditRecord, Any], float],
context: Optional[Mapping[str, Any]] = None,
*,
noise_floor: int = 0
) -> FixReport
reference_classifier(...)¶
A classifier for the common case: a reference answer and a normaliser.
reference_classifier(
normalise: Callable[[str], str],
reference_of: Callable[[Any], str],
*,
ambiguous_when: Optional[Callable[[str, str, Any], bool]] = None,
spec_gap_when: Optional[Callable[[str, str, Any], bool]] = None
) -> Classifier
residual_stats(records: Iterable[AuditRecord]) -> Dict[str, float]¶
n, delta, sigma, disagree over resolved records.
Searching for a fix¶
Enumerate the hard rules, score every combination, rank by the residual. · agentdescent.audit.propose · guide
Candidate(rules: Tuple[str, ...], report: FixReport, passengers: Tuple[str, ...] = ()) -> None¶
One combination of rules and what it does to the whole labelled set.
Rule(name: str, predicate: Predicate) -> None¶
A named reason to mark an output wrong that the verifier marked right.
SearchReport(...)¶
Every combination tried, best first, and the two things to distrust.
SearchReport(
sigma_before: float,
candidates: List[Candidate] = <factory>,
n_rules: int = 0,
n_combinations: int = 0,
floor: float = nan,
warnings: List[str] = <factory>
) -> None
length_rules(...)¶
Candidate rules that need only a reference and a normaliser.
length_rules(
normalise: Callable[[str], str],
reference_of: Callable[[Any], str],
*,
question_of: Optional[Callable[[Any], str]] = None,
short: Sequence[float] = (0.6, 0.4),
long: Sequence[float] = (1.6, 3.0)
) -> List[Rule]
search(...)¶
Score every combination of rules up to max_size, best first.
search(
records: Sequence[AuditRecord],
rules: Sequence[Rule],
context: Optional[Mapping[str, Any]] = None,
*,
max_size: int = 2,
floor: float = nan,
max_combinations: int = 200
) -> SearchReport
The verifier scorecard¶
What has to be true before a new verifier replaces the old one. · agentdescent.audit.scorecard · guide
Cost(verifier_seconds: float, oracle_seconds: float = nan) -> None¶
Seconds per decision, for the verifier and for the thing it stands in for.
Goal¶
Which way a metric is supposed to move.
| member | value |
|---|---|
LOWER |
'lower is better' |
HIGHER |
'higher is better' |
WATCH |
'no target; read it' |
Metric(...)¶
One row. previous is None when there is nothing to compare to.
Metric(
name: str,
value: float,
goal: Goal,
previous: Optional[float] = None,
note: str = '',
blocking: bool = False,
triggered: bool = False
) -> None
RescanReport(...)¶
What a new verifier would have said about outputs the run already scored.
RescanReport(
n: int,
n_artifacts: int,
agreement: float,
mean_shift: float,
sigma_shift: float,
by_artifact: Dict[str, Dict[str, float]] = <factory>,
flipped: List[Tuple[str, str, float, float]] = <factory>,
n_pairs: int = 0,
sigma_before: float = nan,
sigma_after: float = nan
) -> None
Scorecard(...)¶
The rows, and whether they add up to a change worth making.
Scorecard(
version: str,
previous_version: Optional[str],
metrics: List[Metric],
blockers: List[str] = <factory>,
notes: List[str] = <factory>,
rescan: Optional[RescanReport] = None,
rank: Optional[RankReport] = None,
computed_at: float = 0.0
) -> None
rescan(...)¶
Re-score the outputs the audit kept, and see what would have moved.
rescan(
records: Sequence[AuditRecord],
new_verifier: Callable[[AuditRecord, Any], float],
context: Optional[Mapping[str, Any]] = None,
*,
pairs: Optional[Sequence[Tuple[str, str]]] = None
) -> RescanReport
verifier_scorecard(...)¶
Fill the card for current, against previous where there is one.
verifier_scorecard(
current: Rectification,
records: Sequence[AuditRecord],
*,
previous: Optional[Rectification] = None,
previous_records: Sequence[AuditRecord] = (),
rescan_report: Optional[RescanReport] = None,
rank: Optional[RankReport] = None,
cost: Optional[Cost] = None,
previous_cost: Optional[Cost] = None,
max_false_negative: float = 0.05,
max_cost_ratio: float = 0.25
) -> Scorecard
Ordering agreement¶
Can the verifier put candidates in the right order -- the only thing the gate uses. · agentdescent.audit.ranking · guide
Flip(...)¶
One artifact pair the verifier orders backwards.
Flip(
base: str,
candidate: str,
verifier_gap: float,
oracle_gap: float,
n_base: int,
n_candidate: int
) -> None
RankReport(...)¶
Ordering agreement, at the unit level and at the level the gate acts on.
RankReport(
n_units: int,
tau_b: float,
concordant: int,
discordant: int,
one_directional: bool,
n_artifacts: int,
by_artifact: Dict[str, Dict[str, float]] = <factory>,
n_pairs: int = 0,
compared: Tuple[Tuple[str, str], ...] = (),
agree: int = 0,
ties: int = 0,
flips: List[Flip] = <factory>
) -> None
| method | what it does |
|---|---|
above(gap: float) -> Tuple[int, int] |
(agree, flip) among pairs whose verifier gap is at least gap. |
kendall_tau_b(x: Sequence[float], y: Sequence[float]) -> Tuple[float, int, int]¶
(tau_b, concordant, discordant), tie-corrected.
rank_agreement(...)¶
Does the verifier order units, and artifacts, the way ground truth does?
rank_agreement(
records: Iterable[AuditRecord],
*,
pairs: Optional[Sequence[Tuple[str, str]]] = None,
min_units: int = 1
) -> RankReport
Drift monitoring¶
EWMA control charts on the correction, without an alarm every generation. · agentdescent.audit.drift · guide
DriftKind¶
What a signal is telling the operator to do.
| member | value |
|---|---|
BIAS_UP |
'bias-up' |
BIAS_DOWN |
'bias-down' |
SIGNAL_LOST |
'signal-lost' |
NOT_INDEPENDENT |
'not-independent' |
DriftMonitor(...)¶
EWMA charts on delta_hat and gain_factor, generation by generation.
DriftMonitor(
*,
lam: float = 0.2,
L: float = 3.0,
centre: Optional[float] = None,
min_gain: float = 1.05,
gain_lam: float = 0.2
) -> None
| method | what it does |
|---|---|
observe(rect: Rectification, label: str = '') -> List[DriftSignal] |
Chart one generation. Returns only the signals this point raised. |
observe_point(point: DriftPoint) -> List[DriftSignal] |
Chart a point assembled by hand. For a caller that is not using Calibrator. |
DriftPoint(...)¶
One generation's rectification, reduced to what a chart needs.
DriftPoint(
label: str,
delta_hat: float,
se: float,
gain_factor: float,
n: int,
covers: Tuple[float, float] = (0.0, 0.0)
) -> None
DriftReport(...)¶
Every point charted, every signal raised, and whether the chart is valid.
DriftReport(
points: List[DriftPoint] = <factory>,
z_bias: List[float] = <factory>,
band: List[float] = <factory>,
z_gain: List[float] = <factory>,
signals: List[DriftSignal] = <factory>,
centre: float = 0.0,
overlapping: bool = False
) -> None
DriftSignal(...)¶
One alarm, with the number that raised it and what to do.
DriftSignal(
kind: DriftKind,
at: int,
label: str,
value: float,
z: float,
limit: float,
detail: str
) -> None
Coverage allocation¶
Where the improvement labels go: Good-Turing unseen mass, not Neyman. · agentdescent.audit.coverage · guide
Coverage(key: str, labels: int, modes: int, singletons: int, unseen: float) -> None¶
What one key has taught so far, and how much it still has to teach.
CoveragePlan(...)¶
Per-key inclusion probabilities for the improvement pool.
CoveragePlan(
rates: Dict[str, float],
target_n: Dict[str, int],
weights: Dict[str, float],
unseen: Dict[str, float],
default_rate: float = 0.0,
total_n: int = 0,
expected_units: int = 0,
unseen_overall: float = nan,
warnings: List[str] = <factory>
) -> None
coverage_of(...)¶
Group resolved records by key and measure the variety inside each.
coverage_of(
records: Iterable[Any],
key: Callable[[Any], str],
mode: Callable[[Any], Optional[str]],
*,
keys: Sequence[str] = ()
) -> Dict[str, Coverage]
exhausted(coverage: Mapping[str, Coverage], min_unseen: float = 0.05) -> List[str]¶
Keys where the next label is unlikely to show anything new.
plan_coverage(...)¶
Allocate target_n improvement labels by how much each key can still teach.
plan_coverage(
*,
weights: Mapping[str, float],
expected_units: int,
coverage: Mapping[str, Coverage],
target_n: int = 100,
min_per_key: int = 5,
max_rate: float = 1.0
) -> CoveragePlan
rarefaction(...)¶
[(m, mean distinct modes in a sample of m)] -- the diminishing return.
rarefaction(
modes: Sequence[str],
sizes: Sequence[int],
*,
reps: int = 200,
seed: int = 0
) -> List[Tuple[int, float]]
rebalance(...)¶
How much of the audit budget belongs to calibration, given unseen.
rebalance(
unseen: float,
*,
floor: float = 0.5,
ceiling: float = 0.95,
learning_at: float = 0.25
) -> float
unseen_mass(modes: Sequence[Optional[str]]) -> float¶
Good-Turing: the probability that the next label shows an unseen mode.
unseen_mass_overall(coverage: Mapping[str, Coverage]) -> float¶
Good-Turing across every key, pooled by label count.
The merge path's ranking¶
Draining the audit scheduler into the queue a person works from. · agentdescent.audit.queue · guide
DrainReport(...)¶
What came off the queue, and what could not be placed.
DrainReport(
priorities: Dict[str, float] = <factory>,
popped: int = 0,
unplaced: int = 0,
examples: List[str] = <factory>
) -> None
drain(...)¶
Empty the scheduler's queue into signature -> priority.
drain(
scheduler: Any,
*,
signature_of: Optional[Callable[[Any], Optional[str]]] = None,
limit: Optional[int] = None
) -> DrainReport
prioritise(...)¶
Order pending records by what the merge path thought was risky.
prioritise(
records: Iterable[AuditRecord],
priorities: Dict[str, float],
*,
default: float = 0.0
) -> List[AuditRecord]
Audit allocation¶
Neyman allocation, as per-stratum inclusion probabilities. · agentdescent.audit.sampler · guide
AuditPolicy(...)¶
What the audit is trying to achieve, and what it refuses to do to get there.
AuditPolicy(
enabled: bool = False,
target_halfwidth: float = 0.05,
boundary_width: float = 0.05,
calibration_fraction: float = 0.7,
min_per_stratum: int = 20,
min_dominant: int = 80,
max_labels: int = 400,
alpha: float = 0.05
) -> None
| parameter | type | default | what it is |
|---|---|---|---|
enabled |
bool |
False |
Off by default. The whole layer is opt-in, and a policy that is not enabled plans a rate of zero everywhere rather than a small one -- "we are not auditing" and "we are auditing a little" produce different records and only one of them is honest. |
target_halfwidth |
float |
0.05 |
How narrow the correction's 95% interval should be. Drives the total label budget through the Neyman-optimal sample size; see plan. |
boundary_width |
float |
0.05 |
Half-width of the band around the acceptance threshold that counts as boundary. |
calibration_fraction |
float |
0.7 |
Share of audited units that go to the calibration pool rather than the improvement pool. Passed through to the tap. |
min_per_stratum |
int |
20 |
No layer gets fewer than this many labels, whatever Neyman says. A layer allocated two labels contributes a variance estimate from two points, which is worse than not stratifying at all. |
min_dominant |
int |
80 |
The heaviest layer gets at least this many. Defaults to MIN_N_DOMINANT, below which the reported coverage is about 0.92 rather than 0.95 -- so this floor and that warning are the same number for the same reason, and moving one without the other is how a floor stops meaning anything. |
max_labels |
int |
400 |
A hard cap. Oracle labels cost money or a person's afternoon, and a target half-width small enough to be unreachable should produce a warning and a bounded plan rather than an unbounded bill. |
alpha |
float |
0.05 |
SamplePlan(...)¶
Per-stratum inclusion probabilities, and the reasoning that produced them.
SamplePlan(
rates: Dict[str, float],
target_n: Dict[str, int],
weights: Dict[str, float],
resid_sd: Dict[str, float],
default_rate: float = 0.0,
total_n: int = 0,
expected_units: int = 0,
warnings: List[str] = <factory>
) -> None
boundary_stratifier(threshold: float, width: float = 0.05) -> Callable[[Any, str, float], str]¶
Split units into accepted / boundary / rejected around a threshold.
observed_weights(store: Any, verifier_version: str) -> Dict[str, float]¶
Population shares from what a previous run actually saw.
plan_audit(...)¶
Neyman allocation, converted to per-stratum inclusion probabilities.
plan_audit(
policy: AuditPolicy,
*,
weights: Dict[str, float],
expected_units: int,
resid_sd: Optional[Dict[str, float]] = None
) -> SamplePlan
| parameter | type | default | what it is |
|---|---|---|---|
policy |
AuditPolicy |
required | |
weights |
Dict[str, float] |
required | Population share per stratum. Need not sum to exactly 1; it is normalised, because these usually come from counting a previous run and arriving at 0.9999 should not be an error. |
expected_units |
int |
required | How many units the next run is expected to score. Rates are n_h / (W_h * expected_units), so an estimate that is too low oversamples and one that is too high undersamples -- both bounded, and the realised inclusion probability is recorded per unit either way, so a wrong guess costs precision and never correctness. |
resid_sd |
Optional[Dict[str, float]] |
None |
Per-stratum sd of f - Y from the last calibration. Missing entries fall back to an equal-residual assumption, which is what proportional allocation already assumes -- so the first run, with no history, plans proportionally and is right to. |
resid_sd_from(previous: Any) -> Dict[str, float]¶
stratum -> resid_sd out of a PPIResult, or {}.
The calibrator¶
Turns a store of audited pairs into a correction the acceptance gate applies. · agentdescent.audit.calibrator · guide
Calibrator(...)¶
Keeps one rectification per verifier version, and knows when to distrust it.
Calibrator(
store: AuditStore,
*,
alpha: float = 0.05,
seed: int = 0,
min_labels: int = 30,
min_per_stratum: int = 5,
cluster_by: Optional[str] = 'task_id'
) -> None
| parameter | type | default | what it is |
|---|---|---|---|
store |
AuditStore |
required | Where the audited pairs and the unlabelled moments live. |
alpha |
float |
0.05 |
1 - alpha is the nominal coverage of the interval. Must satisfy 0 < alpha < 1; anything else raises ValueError here, at construction, rather than on the first merge. |
seed |
int |
0 |
|
min_labels |
int |
30 |
Below this many resolved calibration labels the result is stale rather than wide. A very wide interval and "we do not know yet" are different claims, and only the second one stops a caller reading a number off it. |
min_per_stratum |
int |
5 |
A stratum with fewer than this many labels is merged into the largest one rather than dropped. Dropping it would silently change the population the estimate describes; merging keeps every unit represented and costs only resolution. |
cluster_by |
Optional[str] |
'task_id' |
Record attribute the audited units are grouped by, "task_id" by default. They are not independent draws: a run scores the same task again for every artifact version, and a task the verifier is generous about it is generous about every time. Measured on the Phase 0 audit -- 177 units from 49 tasks -- treating them as independent made the interval 32% too narrow, and the gate spends that interval's width as drift. None restores the independent estimate, which is right only when each audited unit is a distinct task. |
| method | what it does |
|---|---|
current(verifier_version: str) -> Rectification |
The rectification to apply now, computing it if it is not cached. |
mark_stale(reason: str) -> None |
Withhold every rectification until the next recompute. |
recompute(verifier_version: str) -> Rectification |
Re-read the store and re-estimate. Clears any manual stale mark. |
Rectification(...)¶
The correction, its uncertainty, and whether it may be used at all.
Rectification(
verifier_version: str,
delta_hat: float,
delta_se: float,
theta: float,
theta_ci: Tuple[float, float],
se: float,
n: int,
n_unlab: int,
gain_factor: float,
is_stale: bool,
stale_reason: Optional[str],
resid_sd: float = nan,
warnings: List[str] = <factory>,
computed_at: float = 0.0,
covers: Tuple[float, float] = (0.0, 0.0)
) -> None
| method | what it does |
|---|---|
stale(...) |
A rectification that must not be applied, and says why. |
population_resid_sd(strata) -> float¶
Sd of f - Y over the whole population, from the labelled pairs.
Spending the correction¶
The only place the audit changes an outcome: evidence discounted by verifier noise. · agentdescent.audit.gate · guide
Adjustment(...)¶
What the audit did to one merge decision, and why.
Adjustment(
applied: bool,
reason: str,
delta_hat: float = 0.0,
sigma_eps: float = 0.0,
drift: float = 0.0,
kappa_base: float = 1.0,
kappa_cand: float = 1.0,
var_before: float = 0.0,
var_after: float = 0.0,
audit_limited: bool = False,
stale: bool = False
) -> None
| method | what it does |
|---|---|
to_detail() -> str |
One clause, for the tail of a refusal a person will read. |
RectifiedAcceptance(...)¶
An acceptance gate that knows its measurement came from a proxy.
RectifiedAcceptance(
inner: Any = None,
*,
calibrator: Optional[Calibrator] = None,
verifier_version: Union[str, Callable[[], str]] = '',
rectification: Optional[Rectification] = None,
enabled: bool = True,
drift_allowance: Optional[float] = None,
inflate_when_stale: float = 2.0,
explain_refusals: Optional[bool] = None,
min_kappa: float = 0.001
) -> None
| parameter | type | default | what it is |
|---|---|---|---|
inner |
Any |
None |
The rule that actually decides. Defaults to the shipped gate, with the run's thresholds filled in by the aggregator at install time. |
calibrator |
Optional[Calibrator] |
None |
Where the correction comes from. Re-read on every decision, so a rectification that goes stale mid-run takes effect at the next merge. |
verifier_version |
Union[str, Callable[[], str]] |
'' |
The version to ask the calibrator about -- a string, or a callable returning one for a verifier that can change under the run. |
rectification |
Optional[Rectification] |
None |
A fixed correction instead of a calibrator. For a run that measured its bias once, offline, and does not intend to keep measuring. |
enabled |
bool |
True |
False delegates to inner on the untouched context. This is the constraint that lets the audit be turned on mid-run: off, it is not approximately the old behaviour, it is the old call. |
drift_allowance |
Optional[float] |
None |
Standard deviation to carry for Delta differing between the two sides being compared. None uses the rectification's own se, which is the right order of magnitude and not an estimate of the thing (see the module docstring). |
inflate_when_stale |
float |
2.0 |
Variance multiplier while no usable correction exists. 1.0 passes through instead, which is the choice to treat "we have not measured the verifier" and "the verifier is unbiased" as the same claim. |
explain_refusals |
Optional[bool] |
None |
|
min_kappa |
float |
0.001 |
| method | what it does |
|---|---|
current() -> Optional[Rectification] |
The rectification in force, or None if there is no source. |
explain(ctx) -> Adjustment |
What accept would do to ctx, without deciding anything. |
VerifierWatch(...)¶
Marks a calibrator stale when the instrument it calibrated may have moved.
VerifierWatch(
calibrator: Calibrator,
*,
fingerprint: Optional[Callable[[], str]] = None,
artifact_ids: Iterable[str] = (),
key_globs: Sequence[str] = (),
layers: Iterable[int] = (),
store: Any = None
) -> None
| method | what it does |
|---|---|
check() -> bool |
Re-read the fingerprint; mark stale and return True if it changed. |
on_merge(artifact, diff) -> bool |
Call after a diff commits. True means the calibration was withdrawn. |
discount_for(...)¶
How many of these observations are worth believing, given extra_var.
discount_for(
counts: Tuple[float, float],
extra_var: float,
*,
min_kappa: float = 0.001
) -> Tuple[float, float, float]
rectified_counts(counts: Tuple[float, float], delta: float) -> Tuple[Tuple[float, float], float]¶
Shift (successes, failures) so the rate reads p - delta.
Prediction-powered inference¶
The calibration estimator: a stratified mean that borrows the unlabelled scores. · agentdescent.audit.ppi · guide
PPIError¶
The input cannot support an estimate at all.
PPIResult(...)¶
The estimate, its interval, and everything needed to distrust it.
PPIResult(
theta: float,
ci: Tuple[float, float],
se: float,
df: float,
lambda_: float,
gain_factor: float,
n: int,
n_unlab: int,
alpha: float,
warnings: List[str] = <factory>,
clustered: bool = False,
per_stratum: Dict[str, Dict[str, float]] = <factory>
) -> None
Stratum(...)¶
One layer of the sampling design, with its labelled and unlabelled halves.
Stratum(
name: str,
weight: float,
f_lab: np.ndarray,
y_lab: np.ndarray,
f_unlab: Optional[np.ndarray] = None,
n_unlab: int = 0,
mean_unlab: float = 0.0,
var_unlab: float = 0.0,
clusters_lab: Optional[Sequence[Any]] = None
) -> None
| method | what it does |
|---|---|
from_moments(...) |
Build from a running summary of the unlabelled half rather than its scores. |
cluster_var_of_mean(values: np.ndarray, clusters: Sequence[Any]) -> Tuple[float, int]¶
Variance of mean(values) when the units come in correlated groups.
ppi_mean_stratified(...)¶
Estimate E[Y] over a stratified population, using the unlabelled f.
ppi_mean_stratified(
strata: Sequence[Stratum],
*,
alpha: float = 0.05,
k_folds: int = 5,
seed: int = 0
) -> PPIResult
| parameter | type | default | what it is |
|---|---|---|---|
strata |
Sequence[Stratum] |
required | One Stratum per layer. weight must be the population share and the weights must sum to 1. |
alpha |
float |
0.05 |
1 - alpha is the nominal coverage. 0.05 gives a 95% interval. |
k_folds |
int |
5 |
Folds for cross-fitting lam; see _lambda_crossfit. |
seed |
int |
0 |
Fixes the fold split, so the same labels give the same interval twice. |
t_ppf(p: float, df: float) -> float¶
Quantile of Student's t, accurate for every df > 0.
Audit estimation¶
The design-based baseline: a weighted mean of the residual, with an interval. · agentdescent.audit.estimate · guide
bootstrap_ci(...)¶
Percentile bootstrap interval for hajek_mean.
bootstrap_ci(
values: Sequence[float],
probs: Sequence[float],
*,
draws: int = 5000,
alpha: float = 0.05,
seed: int = 0,
clusters: Optional[Sequence] = None
) -> Tuple[float, float]
hajek_mean(values: Sequence[float], probs: Sequence[float]) -> float¶
Inclusion-probability-weighted mean -- the Hajek ratio estimator.
residual_bias(...)¶
Estimate Delta = E[f - Y] from resolved AuditRecords.
residual_bias(
records: Iterable,
*,
draws: int = 5000,
alpha: float = 0.05,
seed: int = 0
) -> Dict[str, object]
standard_error(...)¶
Bootstrap standard error of hajek_mean.
standard_error(
values: Sequence[float],
probs: Sequence[float],
*,
draws: int = 2000,
seed: int = 1,
clusters: Optional[Sequence] = None
) -> float
The audit out of process¶
The audit's verbs as JSON, for the MCP surface and anything resolving truth later. · agentdescent.audit.service · guide
audit_drift(path: str, versions: Optional[Sequence[str]] = None) -> Dict[str, Any]¶
Chart one rectification per verifier version, oldest first.
audit_pending(...)¶
The records waiting on an oracle -- for a person or an experiment system.
audit_pending(
path: str,
limit: int = 50,
older_than: Optional[float] = None,
version: Optional[str] = None,
order: str = 'dispatched'
) -> Dict[str, Any]
audit_recompute(path: str, version: Optional[str] = None) -> Dict[str, Any]¶
Re-read the store and re-estimate. Returns the new rectification.
audit_rescan(...)¶
Re-score the stored outputs with another verifier and see what moves.
audit_rescan(
path: str,
verifier: str,
version: Optional[str] = None,
allow: Optional[Sequence[str]] = None,
pairs: Optional[Sequence[Sequence[str]]] = None
) -> Dict[str, Any]
audit_resolve(path: str, record_id: str, oracle_score: float) -> Dict[str, Any]¶
Attach ground truth to one pending record.
audit_scorecard(...)¶
Fill the card for version, against previous if one is named.
audit_scorecard(
path: str,
version: Optional[str] = None,
previous: Optional[str] = None,
max_false_negative: float = 0.05,
verifier_seconds: Optional[float] = None,
oracle_seconds: Optional[float] = None
) -> Dict[str, Any]
audit_status(path: str, version: Optional[str] = None) -> Dict[str, Any]¶
The rectifier in force, how much it rests on, and what is outstanding.
Audit records¶
What one audited measurement is, and how a verifier is versioned. · agentdescent.audit.records · guide
AuditRecord(...)¶
One (task, output) pair scored by the verifier, awaiting or carrying truth.
AuditRecord(
record_id: str,
task_id: str,
artifact_signature: str,
output: str,
verifier_version: str,
verifier_score: float,
inclusion_prob: float,
purpose: Purpose,
stratum: str = 'all',
oracle_score: Optional[float] = None,
dispatched_at: float = <factory>,
resolved_at: Optional[float] = None,
sampler_seed: int = 0,
schema_version: int = 2,
scorer_version: str = ''
) -> None
Purpose¶
Which of the two disjoint pools a labelled unit belongs to.
| member | value |
|---|---|
CALIBRATION |
'calibration' |
IMPROVEMENT |
'improvement' |
new_record_id() -> str¶
A fresh record id. Also the ticket a deferred oracle resolves against.
output_digest(output: str) -> str¶
A short stable digest of an output, for logs and for de-duplication.
verifier_fingerprint(fn: Callable[..., Any], *, extra: Any = None) -> str¶
A stable id for the verifier fn, so a correction can be bound to it.
Governance¶
L0 frozen / L1 slow / L2 fast, assigned by blast radius. · agentdescent.governance · guide
GovernanceError¶
Raised when the evolution loop tries to mutate a frozen (L0) artifact.
L1SerialGate(_in_flight: Dict[str, str] = None, _lock: threading.Lock = <factory>) -> None¶
Enforces "at most one L1 diff in evaluation at a time" (section 6).
Layer¶
| member | value |
|---|---|
L2_FAST |
2 |
L1_SLOW |
1 |
L0_FROZEN |
0 |
assert_mutable(artifact: Evolvable) -> None¶
Guard invoked before applying any diff (design doc, section 6, L0).
classify(artifact: Evolvable) -> Layer¶
Assign an artifact to a governance layer.
Staleness policies¶
What to do with a diff proposed against a version that has moved. · agentdescent.staleness · guide
FullStaleness()¶
Use stale diffs directly regardless of eta (max throughput).
GuardedStaleness()¶
Version-gated with rebase in the middle band (AgentDescent's default).
ReflectiveStaleness()¶
Always rebase + re-verify; discard only if the improvement no longer holds.
StaleAction¶
What the aggregator should do with a (possibly stale) evidence card.
| member | value |
|---|---|
ACCEPT |
'accept' |
REBASE |
'rebase' |
DISCARD |
'discard' |
StalenessPolicy¶
get_policy(name: str) -> StalenessPolicy¶
Parallelism methods¶
How a round's work is split across workers: DP / TP / PP. · agentdescent.parallel · guide
ClusterParallel(...)¶
DP over task clusters, leased by UCB instead of sharded round-robin.
ClusterParallel(
cluster_of: Callable[[str], str],
c: float = 1.4,
pass_threshold: Optional[float] = None,
name: str = 'CP'
) -> None
| method | what it does |
|---|---|
observe(unit: WorkUnit, task_id: str, score: float) -> None |
Feed one rollout's outcome back into the cluster's UCB estimate. |
DataParallel(name: str = 'DP') -> None¶
DP -- every worker holds the same artifact; the tasks (keys) are sharded across workers and their diffs are merged. Coverage rotates each round.
ParallelMode¶
| member | value |
|---|---|
DP |
'data_parallel' |
TP |
'tensor_parallel' |
PP |
'pipeline_parallel' |
ParallelStrategy¶
How a round of work is partitioned across n_workers.
PipelineChain(stages: List[str]) -> None¶
An ordered artifact dependency chain, upstream -> downstream.
| method | what it does |
|---|---|
blame(stage_success: Dict[str, bool]) -> Optional[str] |
Back-propagate blame to the earliest failing stage. |
counterfactual_pairs(stage: str) -> List[Tuple[str, str]] |
The {old x new} version swaps to replay for minimal factor analysis. |
PipelineParallel(stages: Sequence[str], name: str = 'PP') -> None¶
PP -- artifacts form a dependency chain; each worker drives one stage, and a downstream failure back-propagates blame to the earliest failing stage (via PipelineChain).
SectionViolation¶
Raised when a worker's diff touches a key outside its section.
TensorParallel(...)¶
TP -- one hot artifact is split into n_sections disjoint sections; each worker owns a section, so edits are conflict-free by construction and the merge is a union (concatenation + a consistency check).
TensorParallel(
n_sections: int,
keys: Optional[Sequence[str]] = None,
route: Optional[Callable[[str], str]] = None,
name: str = 'TP'
) -> None
| method | what it does |
|---|---|
section_map() -> Dict[str, int] |
artifact key -> section. Empty when no key space was declared. |
TensorParallelMerge(n_sections: int, keys: Optional[Sequence[str]] = None) -> None¶
Merge section-scoped diffs into one artifact (concatenation + review).
| method | what it does |
|---|---|
merge(base: Evolvable, section_diffs: List[Tuple[int, Diff]]) -> Tuple[Evolvable, bool] |
Return (merged_artifact, consistency_ok). |
owner_of(key: str) -> int |
Which section owns key -- via the declared partition when there is one. |
WorkUnit(worker: int, keys: List[str], stage: int = 0, section: Optional[int] = None) -> None¶
What one worker is responsible for in one round of a parallel plan.
assign_key_sections(keys: Sequence[str], n_sections: int) -> Dict[str, int]¶
Partition a known artifact key space into balanced, disjoint sections.
assign_sections(worker_ids: Sequence[str], n_sections: int) -> Dict[str, int]¶
Authorize each worker for exactly one section (round-robin).
section_of(key: str, n_sections: int) -> int¶
Hash an artifact key to a section id.
shard_round_robin(items: Sequence, n_shards: int) -> List[List]¶
Split a task list into n_shards disjoint shards, round-robin.
Task sampling¶
Which task a worker rolls out next. · agentdescent.sampling · guide
DifficultyWeighted(...)¶
UCB over tasks, weighted by how much learning signal each one carries.
DifficultyWeighted(
c: float = 0.2,
pass_threshold: Optional[float] = None,
prior: float = 0.5
) -> None
| method | what it does |
|---|---|
stats() -> Dict[str, Tuple[float, float]] |
Copy of the per-task (passes, trials) counters -- for inspection/tests. |
RoundRobin()¶
Cycle through the shard in order -- the deterministic default.
TaskSampler¶
Chooses the next task id for a worker, and learns from the outcome.
| method | what it does |
|---|---|
pick(keys: Sequence[str], round_index: int) -> str |
Return one task id from keys (never mutate keys). |
record(task_id: str, score: float) -> None |
Report the reward a rollout of task_id achieved (0..1). |
Candidate selection¶
Which candidate the next batch of workers starts from. · agentdescent.selection · guide
Archive(...)¶
DGM's and ADAS's archive sampling: performance, tempered by novelty.
Archive(
sampling: str = 'novelty',
temperature: float = 1.0,
seed: int = 0,
rng: Optional['random.Random'] = None
) -> None
Beam(k: int = 1) -> None¶
Keep the k best-scoring candidates and spread the workers over them.
CladeThompson(...)¶
HGM's expansion policy: Thompson sampling on clade metaproductivity.
CladeThompson(
cool_down_beta: float = 0.0,
max_tau: float = 1000.0,
seed: int = 0,
rng: Optional['random.Random'] = None
) -> None
| method | what it does |
|---|---|
tau(budget_remaining: float) -> float |
budget_remaining ** (-cool_down_beta), clamped to [1, max_tau]. |
CostEfficient(...)¶
FlatPuct that pays for exploration out of a budget. Test-time scaling.
CostEfficient(
c_puct: float = 1.0,
cost_exponent: float = 1.0,
prior_exponent: float = 0.0,
anneal: bool = True
) -> None
| method | what it does |
|---|---|
cost_factors(rows: Sequence[Candidate]) -> List[float] |
cost**alpha per row, normalised so the mean is 1. |
MCTS(exploration: float = 1.4) -> None¶
UCT over the candidate tree: one evolve step is one rollout.
MultiHeadUnsupported¶
A policy named a starting point the ledger cannot hold yet.
ParetoFrontier(...)¶
Three published frontier rules, as one class and one argument.
ParetoFrontier(
mode: str = 'per_instance',
k: int = 5,
seed: int = 0,
rng: Optional['random.Random'] = None
) -> None
SelectionContext(...)¶
What a SelectionPolicy is allowed to look at.
SelectionContext(
head: Candidate,
candidates: Sequence[Candidate] = (),
round: int = 0,
n_workers: int = 1,
budget_remaining: float = 1.0
) -> None
SelectionPolicy¶
Given the candidates, return the n starting points for the next batch.
SingleHead()¶
Every worker starts from the current head. Today's behaviour, exactly.
pareto_front(candidates: Sequence[Candidate], *, tasks: Sequence[str]) -> List[Candidate]¶
Candidates no other candidate beats on every task and betters on one.
The population layer¶
What makes a selection policy take effect on a one-branch ledger. · agentdescent.population · guide
PopulationAggregator(...)¶
The shipped merge pipeline plus an archive and a selection policy.
PopulationAggregator(
ledger,
verifier,
audit,
config,
staleness_policy = None,
*,
selection: SelectionPolicy,
artifact_id: str,
meter = None,
conflict = None,
fusion = None,
acceptance = None,
promotion = None,
screen = None,
island_count: int = 0
)
| method | what it does |
|---|---|
checkpoint() -> Optional[dict] |
Serialise the archive and the selection counter. |
finalize() -> None |
Leave the best-scoring candidate on the head, then promote. |
head_for_worker(worker_id: int = 0) -> str |
The branch a worker should snapshot. |
lineage_report() -> List[Dict[str, object]] |
A read-only per-branch view for a population run (#150, semantics C). |
restore(state: dict) -> None |
Restore the archive written by checkpoint. |
step(n_workers: int = 1) -> List[MergeReport] |
Fire every artifact bucket that is ready and return per-artifact reports. |
population_factory(...)¶
The aggregator_factory= adapter for one run.
population_factory(
selection: SelectionPolicy,
artifact_id: str,
*,
meter = None,
conflict = None,
fusion = None,
acceptance = None,
promotion = None,
screen = None,
island_count: int = 0
)
Cross-run consolidation¶
What a run learned survives it: archive, retrieve, mini-eval gate. · agentdescent.consolidation · guide
ConsolidationRecord(...)¶
One archived run: the artifact it produced, and the configuration that produced it.
ConsolidationRecord(
run_id: str,
artifact_id: str,
strategy: str,
config: Dict[str, Any],
state: Dict[str, str],
rendered: str,
final_reward: float,
rounds: int,
trajectory_store: Optional[str] = None,
problem: Optional[Dict[str, Any]] = None,
exam: Optional[str] = None,
exam_flags: int = 0,
exam_note: str = '',
created_at: float = <factory>
) -> None
ConsolidationStore(root: str, *, create: bool = True) -> None¶
A durable archive of what runs learned.
| method | what it does |
|---|---|
archive(...) |
Archive one run's product. Idempotent by run_id (atomic rewrite). |
find(run_id: str) -> Optional[ConsolidationRecord] |
Lookup by identity. |
mark_exam(...) |
Stamp a rule's self-exam status. Idempotent by run_id. |
SeedReport(...)¶
What seed_from_archive decided, and why.
SeedReport(
matched_run: Optional[str],
archived_reward: float,
seeded_score: float,
empty_score: float,
accepted: bool,
reason: str,
sample_n: int
) -> None
config_fingerprint(config: Optional[Dict[str, Any]]) -> str¶
A stable id for the run configuration (dataset, scorer, model, params).
seed_from_archive(...)¶
Retrieve a seed for a new run, gated by a mini-eval on its own tasks.
seed_from_archive(
store: ConsolidationStore,
tasks: Sequence[Task],
run: Any,
reward: Any,
*,
strategy: Any,
artifact_id: str = 'artifact',
config: Optional[Dict[str, Any]] = None,
sample: int = 8,
min_reward: float = 0.0,
seed: int = 0
) -> Tuple[Optional[Dict[str, str]], SeedReport]
Solver-challenger self-play¶
The task distribution evolves too: challenger, frontier, curriculum. · agentdescent.selfplay · guide
Challenger(...)¶
The task-generation artifact's behavior: generate, validate, propose.
Challenger(
generate: Callable[[str, int], Dict[str, Any]],
validate: Callable[[Dict[str, Any]], Task],
propose: Callable[[str, Dict[str, float]], str],
spec: str = '',
config: ChallengerConfig = <factory>
) -> None
ChallengerConfig(...)¶
The challenger's dials: how much of the task budget goes to the frontier, what counts as learned or stuck, and how long a generator may keep trying.
ChallengerConfig(
generated_per_round: int = 4,
frontier_share: float = 0.5,
before_gap: float = 0.05,
stuck_gap: float = 0.0,
max_attempts: int = 8,
r0_floor: float = 0.05,
max_generate_attempts: int = 20,
max_frontier_pool: int = 64
) -> None
FrontierSampler(challenger: Challenger, *, seed: int = 0) -> None¶
A TaskSampler that mixes the fixed seed tasks with a growable frontier pool the challenger generates.
| method | what it does |
|---|---|
count_invalid() -> None |
A validity gate refused a generated payload. |
install(tasks: Sequence[Task]) -> None |
Add a freshly generated, validated frontier batch. |
pick(keys: Sequence[str], r: int) -> Any |
A seed id (from the shard) or a frontier Task object. |
record(task_id: str, score: float) -> None |
Learn one rollout's outcome. The engine calls this for every task, seed or frontier; only frontier ids are tracked. |
sweep(solved_threshold: float) -> Dict[str, float] |
Close one solver round: judge every frontier task that was attempted, return the learnability report for the challenger. |
curriculum_evolve(...)¶
Co-evolve the solver and its task distribution.
curriculum_evolve(
seed_tasks: Sequence[Task],
reward: Reward,
*,
run: Run,
propose: Propose,
strategy: Strategy,
challenger: Challenger,
rounds: int = 15,
n_workers: int = 4,
held_out_frac: float = 0.4,
repo_path: Optional[str] = None,
seed: int = 0,
max_concurrency: int = 1,
solved_threshold: float = 0.999,
verbose: bool = False,
**evolve_kwargs: Any
) -> EvolutionResult
Model-assisted fusion¶
Combine competing values for the same key, when a dict update cannot. · agentdescent.fusion · guide
KeepContradictions()¶
A conflict policy that leaves contradicting diffs for fusion to resolve.
LearnableFusionPolicy(...)¶
A fusion policy that learns when merging wins -- and stops paying for the ranking it cannot change.
LearnableFusionPolicy(
inner: Optional[Any] = None,
*,
tournament: bool = True,
seed: int = 0,
min_trials: int = 20,
uncertainty_threshold: float = 0.15,
epsilon: float = 0.1,
max_wrong_skips: int = 1,
lr: float = 0.2,
reg: float = 0.05,
max_logit: float = 3.0
) -> None
| method | what it does |
|---|---|
bind(verifier: Any) -> None |
Forward the verifier to the inner tournament and the union path. |
select(artifact: Evolvable, diffs: List[Diff]) -> Tuple[Diff, Evolvable, bool] |
Learn or predict: skip the ranking sweep when the union is confidently predicted to win (a learned logistic, not a proof). |
ReflectiveFusion(...)¶
Combine contradicting diffs by asking a model to synthesise their values.
ReflectiveFusion(
complete,
*,
verifier: Any = None,
max_chars: int = 8000,
max_proposals: int = 6,
validate: Optional[Callable[[Any], Any]] = None
) -> None
| method | what it does |
|---|---|
bind(verifier: Any) -> None |
Receive the engine's verifier, if the caller did not supply one. |
select(artifact: Evolvable, diffs: List[Diff]) -> Tuple[Diff, Evolvable, bool] |
Build the union and hand it straight to the acceptance gate. |
reflective_merge(complete, **kwargs) -> Dict[str, Any]¶
The two policies model-merging needs, as Policies keyword arguments.
Borrowed RL decision rules¶
Group-relative advantage, an adaptive trust region, distance from stable. · agentdescent.advantage · guide
AdaptiveTrustRegion(...)¶
Widen the diff-size cap when merges land or oversized refusals dominate; tighten on reversals.
AdaptiveTrustRegion(
*,
initial: TrustRegion = TrustRegion(ops=6, chars=32000),
minimum: TrustRegion = TrustRegion(ops=1, chars=2000),
maximum: TrustRegion = TrustRegion(ops=64, chars=512000),
window: int = 10,
widen: float = 1.25,
tighten: float = 0.5,
accept_rate_to_widen: float = 0.5
) -> None
| method | what it does |
|---|---|
observe(outcome: str) -> TrustRegion |
Record one merge outcome and return the region for the next merge. |
AdvantageAcceptance(inner = None, strength: float = 1.0) -> None¶
Shift the acceptance prior by how well a proposal did against its group.
AdvantageConflict(inner = None, margin: float = 0.5) -> None¶
Break a contradiction by group-relative advantage, not raw score.
GroupAdvantage(min_group: int = 4, max_groups: int = 4096) -> None¶
Standardise a rollout's reward against the group it belongs to.
| method | what it does |
|---|---|
key(base_version: int, cluster: str = '') -> str |
The group a rollout belongs to. Same base, same cluster. |
observe(key: str, reward: float) -> Optional[float] |
Record a reward and return its advantage, or None if unknown yet. |
StableDistanceAcceptance(inner = None, strength: float = 0.1) -> None¶
Penalise candidates that drift far from the confirmed branch.
TrustRegion(ops: int, chars: int) -> None¶
How large one diff may be: operations, and characters.
state_distance(a, b) -> float¶
Fraction of keys on which two artifact states differ, in [0, 1].
Scheduling and audits¶
Duration-aware dispatch, straggler handling, and the oracle audit queue. · agentdescent.scheduler · guide
AuditScheduler(max_queued: int = 4096, collect: bool = False) -> None¶
Allocates oracle budget by estimated value G-hat (design doc, 5.3).
| method | what it does |
|---|---|
force_oracle(blast_radius: float, artifact_id: str) -> bool |
High-impact or low-trust changes are forced through the oracle. |
requeue(item: _AuditItem) -> None |
Put a popped item back with its original priority. |
update_trust(artifact_id: str, oracle_agreed: bool) -> None |
Raise trust when cheap eval agreed with the oracle, lower it when not. |
DurationEstimator(...)¶
Predicts a rollout's wall-clock cost from a task's size (e.g. prompt length), calibrated online from observed rollouts.
DurationEstimator(
prior: float = 0.05,
min_samples: int = 3,
_n: int = 0,
_sx: float = 0.0,
_sy: float = 0.0,
_sxx: float = 0.0,
_sxy: float = 0.0,
_lock: threading.Lock = <factory>
) -> None
ResumeQueue(p90_multiplier: float = 2.0, max_attempts: int = 1) -> None¶
Turn-level checkpoints of timed-out rollouts (partial rollout).
| method | what it does |
|---|---|
pop() -> Optional[ResumeItem] |
Pop the next resume. Counts as a resume for that task, feeding the max_attempts bound in push. |
pop_for(task_ids: Sequence[str]) -> Optional[ResumeItem] |
Pop the first resume whose task belongs to task_ids. |
push(item: ResumeItem) -> bool |
Queue item; False when this task has been resumed too often. |
TaskCluster(...)¶
TaskCluster(
id: str,
tasks: List[Any],
recent_value: float = 0.5,
n_evidence: float = 0.0,
pass_rate: float = 0.5
) -> None
TaskScheduler(clusters: List[TaskCluster], c: float = 1.4) -> None¶
UCB over task clusters, with a difficulty (zero-advantage) filter.
| method | what it does |
|---|---|
lease_one() -> TaskCluster |
Atomically pick the single highest-UCB cluster (async worker pull). |
lease_round_robin() -> TaskCluster |
Async worker pull that spreads concurrent workers across clusters. |
select_batch(k: int) -> List[TaskCluster] |
Lease k clusters to workers, UCB-ordered, cycling if k exceeds the number of clusters. |
fifo_makespan(weights: List[float], n_workers: int) -> float¶
Makespan of naive round-robin dispatch (the baseline LPT improves on).
lpt_schedule(weights: List[float], n_workers: int) -> Tuple[List[int], float]¶
Longest-Processing-Time-first assignment of items to workers.
The data layer¶
Datasets, splits, and cached fetches from HuggingFace or raw URLs. · agentdescent.dataloader · guide
Dataset(...)¶
A dataset partitioned into train / val / test splits.
Dataset(
train: List[Any] = <factory>,
val: List[Any] = <factory>,
test: List[Any] = <factory>,
name: str = ''
) -> None
| method | what it does |
|---|---|
map(fn: Callable[[Any], Any]) -> 'Dataset' |
Apply fn to every item in every split, returning a new Dataset. |
split_dataset(...)¶
Partition items into a Dataset by ratios (train, val, test).
split_dataset(
items: Sequence[Any],
*,
ratios: Tuple[float, float, float] = (0.6, 0.2, 0.2),
seed: int = 0,
shuffle: bool = True,
stratify_key: Optional[Callable[[Any], Any]] = None,
name: str = ''
) -> Dataset
An evolve() call as data¶
The JSON spec a host agent writes and the CLI / MCP server run. · agentdescent.evolvespec · guide
EvolveSpec(...)¶
What to evolve, against what, scored how, by whom -- as data.
EvolveSpec(
kind: str,
target: str,
data: Dict[str, Any],
score: Union[str, Dict[str, Any]] = 'contains',
agent: Optional[Union[str, Dict[str, Any]]] = None,
reflect: Optional[Union[str, Dict[str, Any]]] = None,
name: Optional[str] = None,
template: str = '{skill}\n\n{prompt}',
layout: Optional[str] = None,
prompt_template: Optional[str] = None,
editable: Sequence[str] = ('**',),
frozen: Sequence[str] = (),
max_files_per_diff: int = 2,
validate_skill: bool = True,
entrypoint: Sequence[str] = (),
setup_cmd: Sequence[str] = (),
test_cmd: Sequence[str] = ('python', '-m', 'pytest', '-q'),
timeout: float = 120.0,
host: Optional[str] = None,
env_passthrough: Sequence[str] = (),
audit: Optional[Dict[str, Any]] = None,
algorithm: Optional[Union[str, Dict[str, Any]]] = None,
policies: Dict[str, Any] = <factory>,
agg_config: Dict[str, Any] = <factory>,
evolve: Dict[str, Any] = <factory>,
extra_artifacts: Dict[str, Any] = <factory>,
allow: Sequence[str] = (),
record_rollouts: bool = True,
version: int = 1
) -> None
| method | what it does |
|---|---|
absolutise(base: Optional[str] = None) -> 'EvolveSpec' |
A copy whose file paths are absolute, resolved against base (cwd). |
SpecError¶
A spec that cannot be composed, and the field that is wrong.
compose(...)¶
Turn a spec into the evolve() call the quickstarts would write.
compose(
spec: EvolveSpec,
*,
usage: Optional[Usage] = None,
on_round: Optional[Callable] = None,
repo_path: Optional[str] = None,
workspace_root: Optional[str] = None,
sandbox_pool: Any = None,
**overrides: Any
) -> Composition
load_spec(path: str, *, absolutise: bool = True) -> EvolveSpec¶
Read a spec from a JSON file.
run_spec(spec: EvolveSpec, **hooks: Any) -> EvolutionResult¶
Compose and run. hooks are compose's keyword arguments.
Barrier-free evolution¶
evolve() without the round barrier. · agentdescent.async_evolve · guide
async_evolve(...)¶
Evolve an artifact without a round barrier.
async_evolve(
tasks,
reward: Reward,
*,
agent: Optional[Agent] = None,
run: Optional[Run] = None,
propose: Optional[Propose] = None,
strategy: Optional[Strategy] = None,
initial_state: Optional[Dict[str, str]] = None,
blast_radius: float = 0.2,
artifact_id: str = 'artifact',
n_workers: int = 4,
async_ratio: int = 3,
resync_on_commit: bool = True,
max_seconds: float = 20.0,
max_iters: Optional[int] = None,
max_calls: Optional[int] = None,
max_tokens: Optional[int] = None,
stop_on_diminishing_returns: bool = False,
efficiency_floor: float = 0.25,
call_budget: Optional[CallBudget] = None,
target_reward: Optional[float] = None,
patience: Optional[int] = None,
max_worker_errors: int = 3,
eval_concurrency: int = 8,
pipelined_gate: bool = False,
gate_workers: int = 2,
held_out_frac: float = 0.4,
repo_path: Optional[str] = None,
agg_config = None,
staleness_policy: Optional[StalenessPolicy] = None,
aggregator_factory = None,
oracle_budget: int = 200,
cheap_eval_tasks: Optional[int] = None,
fusion_tournament: Optional[bool] = None,
checkpointing: bool = False,
solved_threshold: float = 0.999,
shuffle: bool = False,
seed: int = 0,
self_verify: bool = True,
shutdown_grace: float = 2.0,
stall_patience: int = 50,
duration_estimator: Optional['DurationEstimator'] = None,
straggler_factor: float = 3.0,
resume_queue: Optional['ResumeQueue'] = None,
task_sampler: Optional['TaskSampler'] = None,
on_round: Optional[Callable[[RoundInfo], None]] = None,
on_reports: Optional[Callable[[Sequence[Any]], None]] = None,
stop_when: Optional[Callable[[RoundInfo], bool]] = None,
verbose: bool = False,
usage: Optional[Usage] = None,
policies: Optional['Policies'] = None,
allocator: Optional[Any] = None,
record_trajectories: bool = False,
trajectory_store: Optional[Any] = None,
contract: Optional[Contract] = None,
population_islands: int = 0,
extra_artifacts: Optional[Dict[str, 'EvolvingArtifact']] = None
) -> EvolutionResult
| parameter | type | default | what it is |
|---|---|---|---|
tasks |
required | Exactly as in evolve, which documents them. (Listed rather than left to the paragraph above: a completeness check that reads prose cannot tell a documented parameter from a mentioned one.) |
|
reward |
Reward |
required | As tasks. |
agent |
Optional[Agent] |
None |
As tasks. |
run |
Optional[Run] |
None |
As tasks. |
propose |
Optional[Propose] |
None |
As tasks. |
strategy |
Optional[Strategy] |
None |
As tasks. |
initial_state |
Optional[Dict[str, str]] |
None |
As tasks. |
blast_radius |
float |
0.2 |
As tasks. |
artifact_id |
str |
'artifact' |
As tasks. |
n_workers |
int |
4 |
Producer threads (>= 1). The train tasks are sharded round-robin across them; a worker with an empty shard is not started. |
async_ratio |
int |
3 |
The lag budget, in two senses: a worker refreshes its snapshot once head drifts more than this far ahead, and it stops producing while more than this many cards sit un-merged. The second bound matters at cold start, before any commit has moved head. |
resync_on_commit |
bool |
True |
Refresh every worker as soon as a sweep commits, whatever the ratio. On by default: a worker that starts a rollout against a version a finished sweep has already replaced is doing work the merger will discard, and no workload wants that. This does not remove staleness where a real workload gets it. A worker snapshots, then spends the rollout in run, then pushes; a commit landing anywhere in that window makes the card stale no matter what the top of the loop does. What it removes is the other source: starting a rollout against a snapshot a finished sweep has already superseded. The two coincide only when rollouts are short relative to sweep cadence -- as they are in this repo's synthetic tests and bench workloads, where a rollout is a dictionary lookup and turning this on does collapse η to 0. Those are the cases that need False: anything measuring what the lag budget alone does has to switch this off, or the budget is no longer the only resync trigger and the measurement is of something else. Turn it on when the artifact's content is what workers reason from, so an out-of-date copy makes the work void rather than merely stale. Evolving a skill library from empty is the case that motivated it: the lag budget fires at head_v - base_v > async_ratio, so with the default 3 the first three commits leave every worker still proposing against no library at all, re-deriving what head already has for the merger to discard. |
max_seconds |
float |
20.0 |
Wall-clock budget for the production phase only. Two things still happen after it, so budget for them: a bounded shutdown (shutdown_grace, since an in-flight rollout cannot be cancelled) and one held-out scoring pass to compute final_reward. That pass is memoised per (artifact, task), so it is free when the final head was already scored by a sweep and costs a full held-out sweep of the backend when it was not -- which is exactly the case when the budget was too short for any sweep to finish. |
max_iters |
Optional[int] |
None |
Stop after this many worker rollouts in total (a budget, not a barrier). |
max_calls |
Optional[int] |
None |
Stop after this many actor invocations (run + propose) in total. The second half of an equal-budget comparison: two configurations matched on rollouts still differ in model spend whenever one of them asks for more proposals per rollout, and the cheaper unit is the one a reader assumes was held fixed. Both bounds are checked as each rollout lands, so a run overshoots only by what was already in flight. |
max_tokens |
Optional[int] |
None |
Stop after this many tokens in total (prompt + completion), as the meter measured them. The third budget unit, and the one cost is measured in: max_calls and max_iters count invocations, and a reasoning model can spend 40k tokens on hidden thinking in a single one, so neither bounds the bill. Checked as each rollout lands (tighter than the synchronous path's round barrier), and a BudgetGovernor degrades optional spend -- fusion tournaments at 75% of the budget, self-verify at 90% -- before the wall. None (default) means unbounded, and no governor is constructed. |
stop_on_diminishing_returns |
bool |
False |
Stop when the run's measured return per token falls off its own peak by more than efficiency_floor. Off by default; needs max_tokens. See evolve. |
efficiency_floor |
float |
0.25 |
How far below the peak counts as diminishing, when the stop above is on. A self-calibrating ratio, not an absolute rate. See evolve. |
call_budget |
Optional[CallBudget] |
None |
An adaptive per-call thinking budget (o1-style test-time scaling). See evolve; the allocation happens before each worker's propose call, gated by the same score and governor.remaining_fraction() the synchronous path uses. |
target_reward |
Optional[float] |
None |
Stop as soon as a sweep's held-out reward reaches this. Compared against the real reward, never against an acceptance probability. |
patience |
Optional[int] |
None |
Stop after this many consecutive merge sweeps that fail to beat the best held-out reward seen so far. The async analogue of the synchronous knob: there are no round barriers here, so a sweep (one drain-and-merge by the merger) is the unit. None disables it. |
max_worker_errors |
int |
3 |
Consecutive failed rollouts before a worker that has never succeeded gives up. Workers that have succeeded at least once never retire; they back off and keep trying until the run's own budget ends it. |
eval_concurrency |
int |
8 |
How many held-out tasks the merger scores at once. 1 restores the old sequential behaviour. |
pipelined_gate |
bool |
False |
Run a merge's measurement phase on its own threads instead of on the merger. Off by default. The merger is one thread and it does three things per merge: drain and filter (cheap), score the base and the candidate (expensive), then accept, audit and commit (cheap). Measured on the stub workload, the middle phase is 94% of the merger's gate time and the merger is ~90% busy, which leaves 4.5 of 8 workers blocked at the backpressure gate at any moment (docs/efficiency.md). This lets the merger go back to draining while the measurement runs, so the workers keep producing. It changes no commit semantics. At most one candidate per artifact is measured at a time, so every candidate is still committed against the head it was prepared and measured on -- there is no candidate-level staleness to have a policy about. Cards arriving meanwhile accumulate in the aggregator's buffer, which is what the buffer is for, so batches get larger rather than more numerous. Requires an aggregator with begin_step / measure / finish_step (the shipped one has them). A custom one that predates the seam warns and keeps the inline path. |
gate_workers |
int |
2 |
Threads for the measurement phase when pipelined_gate is on. Bounded in practice by one candidate per artifact, so the default of 2 is enough for a single-artifact run with one measurement finishing as the next starts. This is a third pool -- n_workers rollouts, gate_workers measurements, each of which fans out over eval_concurrency tasks -- so the ceiling your provider sees is n_workers + gate_workers * eval_concurrency. |
held_out_frac |
float |
0.4 |
As tasks. |
repo_path |
Optional[str] |
None |
As tasks. |
agg_config |
None |
As tasks. |
|
staleness_policy |
Optional[StalenessPolicy] |
None |
As tasks. |
aggregator_factory |
None |
As tasks. |
|
oracle_budget |
int |
200 |
As tasks. |
cheap_eval_tasks |
Optional[int] |
None |
As in evolve: how many held-out tasks the cheap layer scores when ranking candidates. None is 8, or the whole held-out set when that is smaller. |
fusion_tournament |
Optional[bool] |
None |
As in evolve: rank the survivors against their fusion before putting one forward. None defers to agg_config, which is off. The cost/benefit is identical on this path -- there is one merger thread here too, and it pays the ranking on the critical path of every commit. |
checkpointing |
bool |
False |
As in evolve: save the aggregator's search state each round so a later run on the same repo_path resumes the search, not just the artifact. Off by default. The merger is the only thread that records a round here, so the write sits on the same single-writer path it does on the synchronous side. |
solved_threshold |
float |
0.999 |
As in evolve: the reward at which a task counts as solved and no proposal is requested. Lower it for a graded scorer. |
shuffle |
bool |
False |
As in evolve: shuffle before the positional train/held-out split. Off by default. |
seed |
int |
0 |
As shuffle. |
self_verify |
bool |
True |
As in evolve. False skips the extra per-trajectory rollout, which is what ports that judge candidates only on held-out want. |
shutdown_grace |
float |
2.0 |
Total seconds to wait for the worker and merger threads after the budget expires -- shared across all of them, not per thread. An in-flight rollout cannot be cancelled, so a slow backend can still overrun it; a warning says so and work already merged is kept. |
stall_patience |
int |
50 |
Merger sweeps that may pass with cards arriving and nothing committing before every worker is forced to resync, regardless of async_ratio. Without it a lag budget larger than the staleness tolerance livelocks under the Guarded policy: workers propose against a snapshot too old for the policy to accept, every card is discarded, head never moves, and the lag budget therefore never triggers a refresh either. |
duration_estimator |
Optional['DurationEstimator'] |
None |
Pass a DurationEstimator to fit seconds ~ intercept + slope * len(prompt) online and count rollouts that overran their own estimate by more than straggler_factor (result.stragglers). This is the design's L-traj mechanism, which until now lived only in the reference runtime and so was unreachable from the API a real workload uses. Detection only: resuming a partial rollout would need it to expose its turns, and run(rendered, task) -> output is opaque. |
straggler_factor |
float |
3.0 |
As duration_estimator. |
resume_queue |
Optional['ResumeQueue'] |
None |
Turn a counted straggler into a re-run. A shared ResumeQueue passed here receives every detected straggler's task and the version it measured; the next idle worker whose shard owns that task re-runs it against the current head. A straggler measured version N, its re-run measures version N+k, so the pair is a free cross-version A/B signal. Detection is after-return -- a rollout that overruns its estimate is noticed once it has finished, not cancelled mid-flight. None (the default) keeps the old behaviour exactly: stragglers are counted and dropped. result.resumed reports the re-runs. |
task_sampler |
Optional['TaskSampler'] |
None |
Which task a worker takes next from its shard. |
on_round |
Optional[Callable[[RoundInfo], None]] |
None |
Called with each RoundInfo as a merger sweep completes -- progress for a long run. It runs on the merger thread and must be cheap and thread-safe; an exception is reported, not fatal. |
on_reports |
Optional[Callable[[Sequence[Any]], None]] |
None |
Called with the sweep's MergeReports before on_round reduces them to counters; each carries the diff the merger measured, accepted or not (#452). Same thread and same rules as on_round. |
stop_when |
Optional[Callable[[RoundInfo], bool]] |
None |
Asked after on_round with the same RoundInfo; True ends the run with stop_reason="stop_when" -- the caller's own budget (dollars, a deadline, a kill file), checked between merger sweeps like the built-in bounds. Same thread and the same rules as on_round. |
verbose |
bool |
False |
Print one line per merger sweep. |
usage |
Optional[Usage] |
None |
Share one Usage with your model adapters (claude(usage=u), openai_compatible(usage=u)) and the result's token counts become real. Without it the run still reports calls, seconds and failures -- run is (rendered, task) -> str, so an opaque actor has no way to surface tokens, and inventing a number would be worse than reporting zero. |
policies |
Optional['Policies'] |
None |
Bundle of replaceable pieces (Policies). Every field defaults to None meaning "current behaviour", so Policies() and passing nothing are the same run. The individual keyword arguments -- task_sampler, staleness_policy, aggregator_factory -- are shortcuts onto its fields and keep working; an explicit argument wins over a bundle default rather than being silently ignored. Fields whose implementations have not landed yet raise rather than being accepted and ignored: a caller who passes a custom acceptance rule and sees a finished run would reasonably conclude it ran. New capabilities go here rather than adding another parameter to a function that already has thirty-five. |
allocator |
Optional[Any] |
None |
Exactly as in evolve: value-directed budget allocation (see allocator). None (the default) is the old behaviour exactly. Pass a value allocator and, once the token budget is tight, the self-verify rollout is spent only on candidates the allocator expects to commit; the merger reports every outcome back so it learns. Requires max_tokens. Counterfactual exploration corrects a biased value model rather than reinforcing it. |
record_trajectories |
bool |
False |
Exactly as in evolve: persist every rollout and gate evaluation as a Trajectory under <repo_path>/trajectories/. Off by default; pass trajectory_store= to control where records land. |
trajectory_store |
Optional[Any] |
None |
An explicit TrajectoryStore to record into. None + record_trajectories builds the default store at <repo_path>/trajectories. |
contract |
Optional[Contract] |
None |
Exactly as in evolve: the artifact's contract, where governance invariants are declared. None (the default) is the reference skill contract, which declares nothing and enforces nothing. |
population_islands |
int |
0 |
Persistent head-islands for a population run. Exactly as in evolve; 0 (the default) keeps the per-step-fork behaviour. |
extra_artifacts |
Optional[Dict[str, 'EvolvingArtifact']] |
None |
Exactly as in evolve: additional artifacts to register alongside the primary, as {aid: EvolvingArtifact}. The barrier-free worker proposes against each of them too (no parallel strategy, so it is the primary plus every extra), and the aggregator merges their diffs by target. Empty (the default) is the old single-artifact behaviour exactly. |
The async orchestrator¶
The reference barrier-free runtime and its statistics. · agentdescent.async_runtime · guide
AsyncAgentDescent(...)¶
Barrier-free reference runtime, on the general engine.
AsyncAgentDescent(
repo_path: str,
universe: TaskUniverse,
config: Optional[AsyncConfig] = None,
agg_config: Optional[AggregatorConfig] = None,
staleness_policy: Optional[StalenessPolicy] = None,
estimator: Optional[DurationEstimator] = None,
skill_id: str = 'mol-router',
aggregator_factory = None,
rollout = None
) -> None
| method | what it does |
|---|---|
buffer_pending() -> int |
Cards waiting in the aggregator's buckets, or 0 before a run. |
AsyncConfig(...)¶
AsyncConfig(
n_workers: int = 6,
async_ratio: int = 3,
resync_on_commit: bool = True,
noise: float = 0.15,
target_accuracy: float = 0.98,
max_seconds: float = 20.0,
oracle_budget: int = 400,
stall_patience: int = 150,
duration_timeout_factor: float = 3.0,
seed: int = 0,
self_verify: bool = True
) -> None
AsyncStats(...)¶
AsyncStats(
rollouts: int = 0,
proposals: int = 0,
sweeps: int = 0,
commits: int = 0,
fused: int = 0,
discarded_stale: int = 0,
conflicts_dropped: int = 0,
forced_refreshes: int = 0,
stragglers_checkpointed: int = 0,
retired_workers: int = 0,
oracle_used: int = 0,
final_dev_accuracy: float = 0.0,
final_stable_accuracy: float = 0.0,
wallclock: float = 0.0,
error: Optional[str] = None,
timeline: List[Tuple[int, float]] = <factory>
) -> None
The reference orchestrator¶
The round loop the research results were measured with. · agentdescent.orchestrator · guide
AgentDescent(...)¶
The merge-based parallel self-evolution system, on the general engine.
AgentDescent(
repo_path: str,
universe: TaskUniverse,
n_workers: int = 6,
noise: float = 0.15,
refresh_interval: int = 2,
skill_id: str = 'mol-router',
config: Optional[AggregatorConfig] = None,
oracle_budget: int = 300,
seed: int = 0,
staleness_policy = None,
self_verify: bool = True
) -> None
RoundStat(...)¶
RoundStat(
round: int,
dev_accuracy: float,
stable_accuracy: float,
committed: int,
fused: int,
discarded_stale: int,
conflicts_dropped: int,
oracle_used: int
) -> None
run_fork_baseline(...)¶
DGM-style archive/fork control: parallel but never merged (RQ1).
run_fork_baseline(
universe: TaskUniverse,
n_workers: int = 6,
noise: float = 0.15,
rounds: int = 40,
seed: int = 0
) -> float
Document backends¶
A tool-using agent over a document that is too big for a prompt. · agentdescent.backends · guide
AgentBackend¶
A base agent that answers a question about a document, possibly using tools.
document_agent(...)¶
Turn any Completion into an AgentBackend for document questions.
document_agent(
completion: Completion,
*,
doc_filename: str = 'document.txt',
inline_chars: int = 200000,
skills_dir: str = '.claude/skills'
) -> AgentBackend
openhands(...)¶
A real OpenHands agent (SDK v1.x) as a workspace-bindable Completion.
openhands(
model: str = 'openai/deepseek-v4-pro',
*,
base_url: str = 'https://api.deepseek.com',
api_key_env: str = 'OPENAI_API_KEY',
temperature: float = 0.0,
max_iterations: int = 40
) -> '_OpenHandsAgent'
openhands_backend(...)¶
document_agent(openhands(...)) -- the document task on OpenHands.
openhands_backend(
model: str = 'openai/deepseek-v4-pro',
*,
base_url: str = 'https://api.deepseek.com',
api_key_env: str = 'OPENAI_API_KEY',
temperature: float = 0.0,
max_iterations: int = 40,
doc_filename: str = 'document.txt'
) -> AgentBackend
tool_loop_backend(complete: Completion, *, max_steps: int = 5, window: int = 3) -> AgentBackend¶
A dependency-free grep/read ReAct loop over the document.
Ready-made scorers¶
The reward functions everyone writes, with the details right. · agentdescent.rewards · guide
GraderError¶
A command_scorer command failed or printed something that is not a score.
command_scorer(...)¶
Grade with any program: the task as JSON on stdin, a float on stdout.
command_scorer(
cmd: Union[str, Sequence[str]],
*,
timeout: float = 60.0,
cwd: Optional[str] = None
) -> Callable
contains(gold_key: str = 'gold', *, normalise: bool = True) -> Callable¶
1.0 when the gold answer appears anywhere in the output.
exact_match(gold_key: str = 'gold', *, normalise: bool = True) -> Callable¶
1.0 when the output equals the gold answer.
last_number(gold_key: str = 'gold', *, tolerance: float = 0.0) -> Callable¶
1.0 when the last number in the output matches the gold number.
numeric_close(gold_key: str = 'gold', *, tolerance: float = 0.01) -> Callable¶
last_number with a relative tolerance -- for rounded answers.
scorer(score) -> Callable¶
Resolve score -- a name from SCORERS or a (task, output) -> float callable -- into the reward evolve() takes.
Equal-budget baselines¶
merge-of-N against best-of-N fork and serial, on one rollout budget. · agentdescent.baselines · guide
ArmResult(...)¶
One arm, one seed, and the spend it actually incurred.
ArmResult(
arm: str,
seed: int,
width: int,
rollouts: int,
calls: int,
prompt_tokens: int,
completion_tokens: int,
wallclock: float,
wallclock_parallel: float,
dev_reward: float,
test_reward: Optional[float],
test_oracle: Optional[float] = None,
forks: Tuple[ForkOutcome, ...] = (),
stop_reason: str = '',
error: Optional[str] = None,
fusion: Optional['FusionStats'] = None
) -> None
Budget(rollouts: int, calls: Optional[int] = None, tokens: Optional[int] = None) -> None¶
What every arm is allowed to spend.
| method | what it does |
|---|---|
split(ways: int) -> 'Budget' |
The share of this budget one of ways independent runs may spend. |
Comparison(...)¶
Several seeds of several arms, and whether they are comparable at all.
Comparison(
arms: Dict[str, List[ArmResult]],
fixed: str = 'rollouts',
unequal: List[Tuple[str, str, float, float]] = <factory>,
confounded: List[Tuple[str, str, float, float]] = <factory>,
tolerance: float = 0.1
) -> None
| method | what it does |
|---|---|
scored(arm: str) -> int |
Seeds of arm that produced a test score at all. |
separates(a: str, b: str, *, min_seeds: int = 3) -> bool |
Whether a's seeds are all above b's, with no overlap. |
spread(arm: str) -> Optional[Tuple[float, float, float]] |
(min, median, max) test quality. Not a confidence interval. |
underpowered(*arms: str, min_seeds: int = 3) -> bool |
Whether any named arm has too few seeds to support a comparison. |
ForkOutcome(...)¶
One member of a fork arm, kept so the selection step can be audited.
ForkOutcome(
seed: int,
dev_reward: float,
test_reward: Optional[float],
rollouts: int,
calls: int
) -> None
Workload(...)¶
The half of the comparison that must not vary, in one object.
Workload(
tasks: Sequence[Task],
reward: Reward,
test_eval: Callable[[EvolutionResult], float],
agent: Optional[Any] = None,
run: Optional[Any] = None,
propose: Optional[Any] = None,
strategy: Optional[Any] = None,
evolve_kwargs: Mapping[str, Any] = <factory>
) -> None
best_of_n_fork(...)¶
N runs that never see each other, each on its share of the budget.
best_of_n_fork(
workload: Workload,
n: int,
*,
budget: Budget,
seed: int = 0,
concurrency: int = 1
) -> ArmResult
compare(...)¶
Group arm results by arm and check what the comparison actually held fixed.
compare(
results: Sequence[ArmResult],
*,
fixed: str = 'rollouts',
tolerance: float = 0.1
) -> Comparison
merge_of_n(...)¶
N workers proposing into one artifact, merged every round. The claim.
merge_of_n(
workload: Workload,
n: int,
*,
budget: Budget,
seed: int = 0,
usage: Optional[Usage] = None
) -> ArmResult
serial(...)¶
One worker, improving itself in sequence. The floor.
serial(
workload: Workload,
*,
budget: Budget,
seed: int = 0,
usage: Optional[Usage] = None
) -> ArmResult
to_markdown(comparison: Comparison) -> str¶
A table whose caption cannot claim more than the numbers support.
Type aliases and constants¶
Values rather than classes or functions.
AcceptDecision¶
Commit or not, and -- when not -- which of the merge categories it was.
AcceptancePolicy¶
Whether a candidate is committed.
AdmitVerdict¶
The composed decision: pass / flag / demote and why.
AgentNode¶
One node of a society: a name, an instruction (a callable), and a budget (how many uses the node may spend).
AggregatorFactory¶
(ledger, verifier, audit, config, policy) -> AggregatorProtocol — how a custom optimizer is installed.
AlgorithmArm¶
One algorithm/seed arm of a tournament.
AlgorithmScore¶
One algorithm's aggregate over its seeds.
AlgorithmTournamentReport¶
Every arm, the per-algorithm aggregate, the winner and any tie.
AllocatorContext¶
One candidate, as the allocator sees it.
AllocatorPolicy¶
Decide, per candidate, whether the expensive part of a merge is worth it.
AppendRules¶
Accumulate a deduped list of rules/lessons (append-only, content-addressed).
ApprovalGate¶
Require a human decision for changes above a risk/size threshold.
ApprovalRequest¶
One high-risk candidate's approval request and its latest decision.
ApprovalStore¶
Append-only JSONL decisions for high-risk candidates.
BootstrapResult¶
A paired bootstrap of per-task gains.
BudgetGovernor¶
Tracks token spend against a cap and decides what to degrade.
CALIBRATION_CEILING¶
Where a calibration share sits when the improvement pool is still finding new error modes, and where it goes once the pool has stopped. Both are policy choices, not measurements: what a label is worth in each pool depends on whether you are trying to fix the verifier or to correct for it.
CALIBRATION_FLOOR¶
Where a calibration share sits when the improvement pool is still finding new error modes, and where it goes once the pool has stopped. Both are policy choices, not measurements: what a label is worth in each pool depends on whether you are trying to fix the verifier or to correct for it.
CacheProtocol¶
Somewhere to keep evaluations. In one process, across many, or on disk.
CallBudget¶
A mutable per-call max_tokens, set by the engine before each expansion.
CampaignReport¶
Every session, the best artifact and where it lives.
CampaignSession¶
One evolution session inside a campaign.
CanaryResult¶
The paired sign test and the deployment verdict.
CapabilityGuard¶
Reject a self-modification that regresses a protected capability.
CapabilityReport¶
What the protected suites measured for one candidate.
CapabilitySuite¶
Tasks a self-modification must not regress, and the bar it must clear.
ChainAuditReport¶
What a chain_audit pass found, ring by ring.
ChainLink¶
One ring of an adoption's evidence chain.
CoevolutionReport¶
The curriculum's effect on verifier/oracle agreement.
CoevolutionRound¶
One curriculum step: the selected tasks, the bias before/after.
Completion¶
Callable[[str], str] — the one contract every model and agent satisfies.
ConflictPolicy¶
Which of a batch of mutually contradictory changes survive.
ContaminationReport¶
How much of the validation text appears in the training text.
ControllerConfig¶
Bounds and hooks for the multi-session loop.
ControllerReport¶
The sessions run, the best reward and why the loop stopped.
CuriousReplayReport¶
What curious_replay_evolve did: the weights, the gains on the still-curious vs mastered past, and the value produced.
DecisionLog¶
Append-only JSONL decisions.
DecisionRecord¶
One immutable adoption/session decision.
DefaultConflict¶
Drop contradicting diffs, keeping whichever scores better (PCGrad-style).
DefaultFusion¶
Build the union of complementary diffs and hand it to the gate.
DiagnosisReport¶
One run's failure mode, the evidence, and what to try next.
EDIT_PROTOCOL¶
The multi-file proposal format a FileTree reflector is told to emit.
EloRating¶
A minimal Elo rating over pairwise matches (K-factor 32).
EvaluationReport¶
The verifier/oracle comparison for one artifact.
EvaluationRow¶
One audited task's verifier and oracle scores.
EvidenceSnapshot¶
The reconciled view: slots, ports, and the archive's health counters.
ExaminedRule¶
One archived rule's self-exam: what the oracle said about it.
Executor¶
Runs rollouts somewhere. Threads here, processes and hosts later.
FAST_MAX¶
The L2/L1 blast-radius boundary (0.30).
FLIP_ALARM¶
Flip rate above which a rescan is called out rather than reported. The plan's number. It is a policy choice and not a measurement -- there is no rate at which silently re-scoring a run's history becomes fine.
FROZEN_IDS¶
Artifact ids the loop may read but never mutate (L0).
FastRuleReport¶
How an evolved rule does against the shipped seed, on both axes.
FileCache¶
A directory of evaluations, so separate processes can share them.
FusionPolicy¶
How complementary diffs become one candidate.
FusionTrial¶
One tournament: what the fused candidate scored against the best single.
GuardDecision¶
One guard's name and decision.
GuardPipeline¶
Run guards in order; the first refusal is the pipeline's decision.
GuardPipelineResult¶
The full ordered trail, including the first refusal when there is one.
GuardedRun¶
The evolution result, the recorded decision and the acceptance chain.
Harness¶
One algorithm port, named and runnable.
HarnessEvidence¶
What the archive says about one algorithm port (harness) on a domain.
HarnessReport¶
What a compose_run did, and why.
HarnessRun¶
One harness's measured outcome in a compose_run.
ImagineReport¶
What imagine_then_evolve did: the verified value, its real gain over the seed, the simulator's trust score, and how much of the budget was spent dreaming vs waking.
JEVModel¶
A linear student verifier trained on oracle labels.
JointMetaReport¶
What a joint_meta_run decided, per slot and jointly.
KeyedRules¶
One entry per category: competing proposals contradict and are resolved.
KillSwitch¶
A file-based global stop for further self-improvement sessions.
LAYOUTS¶
Where a runner writes the evolving tree inside a workspace (claude_skill, skill_library, claude_agent, dsh_skill, agents_skill, root).
LayerBudget¶
One spending line of the framework: a limit, what it spent, what it bought.
LeakageGuard¶
Reject a candidate whose rendered artifact contains protected text.
LeakageReport¶
What the guard found and which protected strings matched.
LearnableMetaJudge¶
A small linear regressor from outcome_features to oracle quality, fit by least squares (pure Python, deterministic).
LearnedSimulator¶
A pure-Python nearest-neighbour simulator from observed rollouts.
LearningValueModel¶
A tiny online logistic model over the candidate features.
LedgerFailure¶
The exception tuple a caller catches to treat any ledger problem as recoverable.
LedgerProtocol¶
Eight methods: four the aggregator calls, four more the engine calls.
LocalWorkspaceSandbox¶
A throwaway directory on this machine -- what a rollout has always got.
MAX_COMBINATIONS¶
Combinations the search will build before it complains. Not a performance limit -- each one is a pass over the labelled set, which is a few hundred rows -- but a statistical one: every combination scored is another chance for one to look good by luck, and a search over a thousand of them on two hundred labels is fitting the noise.
MIN_N_DOMINANT¶
Labels the heaviest stratum needs before the reported interval means what it says. Below it, measured coverage is about 0.92 against a nominal 0.95 -- see pitfall 3 in the module docstring, and test_known_limitation_small_dominant_stratum.
MIN_UNSEEN¶
Below this estimated probability of a new mode, more improvement labels are buying the flat part of the rarefaction curve. Not a law -- the point at which a one-in-twenty chance of learning something stops being worth an oracle call is a budget question -- but it is where the Phase 0 curve had flattened.
MarginalGainStop¶
Stop when reward gained per additional call falls below a bar.
MatchResult¶
One pairwise match: who played whom, on whose ground, and who won.
MemoryCache¶
In-process, single-flight, counted.
MergeContext¶
Everything an AcceptancePolicy is allowed to look at.
MetaAuditRecord¶
One sampled (problem, seed) before/after pair, awaiting truth.
MetaAuditStore¶
Durable JSONL of sampled meta pairs and verdicts, indexed in memory.
MetaChallengerConfig¶
The problem-challenger's dials: how much of the problem budget goes to the frontier, what counts as learned or stuck, and how long a generator may keep trying. The same numbers curriculum_evolve uses, one level up.
MetaExamReport¶
What an exam_archive pass did, and why.
MetaPopulationReport¶
What a meta_population_run did, and why.
MetaProblemChallenger¶
The problem-generation artifact for the meta loop.
PLUGIN_FROZEN¶
Frozen by default for each host, on top of whatever the spec freezes. Hooks and permission config are the plugin's own L0: a hook that blocks a tool call is a guard, and an optimizer that could loosen it to score better would be optimising the guard away. Lockfiles are regenerated by tooling in setup, not authored by a model. Tests are frozen for the same reason as in code_runner: the shortest path to a high score is to weaken the yardstick.
PLUGIN_HOSTS¶
The hosts a plugin-kind spec may name. Paths are relative to the workspace.
PRIORITY_SEED¶
Upstream ERA's flat PUCT -- rank + c * P(s,a) * sqrt(N) / (1 + n) with c = 1 and a uniform prior. PrioritySelection on this source expands the same node as FlatPuct(c_puct=1.0) at every step.
Policies¶
Every replaceable piece, in one argument.
PoliciesBundle¶
The joint artifact: a bundle of per-slot SlotSpec values.
PopulationIsland¶
One island of a meta_population_run: a rule's search, and how it went.
PortfolioReport¶
What a portfolio_report pass saw, and decided.
ProbeRow¶
One task's verifier and oracle scores, and how far apart they are.
ProcessExecutor¶
Persistent worker processes, with re-dispatch when one dies.
Promotion¶
One artifact the PromotionPolicy believes stable should hold.
PromotionPolicy¶
Which artifacts dev has proved well enough to copy onto stable.
ProposalContext¶
What a ProposalPolicy is given for one rollout.
ProposalPolicy¶
How a rollout becomes candidate changes.
ROUTE_SEED¶
The shipped route rule: answer directly unless the task is hard and the budget allows a search. Deliberately conservative -- it refuses to search when the budget is low, which is exactly the decision an evolved rule can improve on (searching hard tasks pays more than the budget it costs).
RSIMetrics¶
Aggregate counts and cost for a stream of decisions.
RSIReport¶
The archive's longitudinal view, plus the current portfolio health.
RationaleReport¶
What rationale_evolve distilled: how many rationales the skill now carries, and the skill itself.
ReallocationPolicy¶
The evolvable reallocation rule: four numbers, applied to a layer window.
RecursiveRound¶
One outer round: the inner self-improvement session and the policy move.
RecursiveSelfImproveReport¶
The outer session: every round, the final rule, and both gains.
RedTeamReport¶
What a red_team_evolve round did: the attack's findings and the coverage before and after the retraining.
Ref¶
A callable named rather than sent: "module:attribute" plus config.
RefError¶
A reference could not be resolved, and why -- never a bare ImportError.
ReplayExample¶
One replayed past example: the task it came from, its recorded outcome, and the rolling statistics that decide how curious the artifact still is.
Result¶
What one rollout produced, or why it did not.
RiskContext¶
The facts a policy may match on.
RiskDecision¶
The action a policy chose and the rule that produced it.
RiskPolicy¶
First-match risk rules with a fail-closed default.
RiskRule¶
One ordered rule; every supplied condition must match.
RolloutSpec¶
One rollout, described completely enough to run somewhere else.
RunManifest¶
The hashed inputs of one run and the resulting stable id.
SCHEMA_VERSION¶
Bumped when the on-disk shape of a record changes incompatibly. A reader that meets a record it does not understand must say so rather than guess -- see load. Version 2 adds optional scorer_version; this build still migrates version-1 lines. Stamp the new marker whenever the persisted shape is one an older v1-only reader cannot decode, so that reader hits its explicit unsupported-version path instead of treating a valid line as corrupt and silently skipping pending work (#315).
SCORERS¶
Named scorers accepted by scorer. Pass a callable for anything else.
SLOTS¶
The decision plane -- the Policies fields a run may evolve. The machinery fields (verifier, ledger, executor, evaluator, eval_cache, aggregator_factory, sandbox_*) are the training code and stay frozen.
SLOT_PROTOCOLS¶
The contract each slot's value must satisfy -- the engine's own Protocols, all runtime_checkable, so a compiled class is checked structurally.
SOLVED¶
Reward at or above which a task counts as solved (0.999). Lower it for a graded scorer, or every rollout asks the reflector to fix an answer that was already good.
STALE_INFLATION¶
Variance multiplier the acceptance gate applies while a rectification is stale. Not a correction -- a stale rectifier has no number to correct with -- but a widening, so a gate that cannot be told how biased its verifier is commits less rather than the same amount with more confidence.
SafetyCheck¶
One named check and why it passed, warned or failed.
SafetyReport¶
The archive's trust posture, with every check that produced it.
Sandbox¶
One acquired execution environment.
SandboxPool¶
The single gate on how many sandboxes exist at once.
SandboxProvider¶
Where sandboxes come from and go back to.
SandboxSpec¶
What environment one rollout needs. Must survive JSON: it crosses processes.
SearchTree¶
A state tree over rendered artifact values, searched by UCB.
SelfImproveConfig¶
What the loop improves, on what, and how hard.
SelfImproveReport¶
The whole session: every tick, whether anything improved, total gain.
SelfImproveTick¶
One pass of the loop: targets, per-slot verdicts, and the budget move.
SelfModifyReport¶
What improve_source accepted: the patch, its tests, its gain.
SelfRewardingReport¶
What a self_rewarding_meta_run did: the evolved value, the judge's measured agreement with the oracle on validation outcomes, whether the judge actually fitted (or fell back to auc), how many oracle labels it trained on, and the value's oracle quality vs the seed's on the held-out problems.
SessionOutcome¶
What one session produced.
SessionRecord¶
One session's outcome inside the controller report.
SharedSandboxPool¶
A pool whose ceiling is the machine's, not this process's.
SingleSlot¶
The artifact is one value, and each accepted proposal replaces it.
SlotEvidence¶
What the archive says about one decision slot's evolution.
SlotRSI¶
One decision slot's adoption history and whether it is still rising.
SocietyGraph¶
A directed graph of AgentNode with delegation edges.
SocietyReport¶
What society_evolve found: the evolved graph, its cost-adjusted correctness on validation tasks, and the same for the seed.
Strategy¶
Defines what evolves and how -- the representation and the merge rule.
TEST_FAILURE_MARKER¶
Prefix of the output code_runner produces when the frozen gate fails, so the failure scores 0 and the reflector can read it.
ThreadExecutor¶
The default: a bounded pool of threads in this process.
TournamentReport¶
The ratings a tournament produced, and the matches that moved them.
ValueBudgetAllocator¶
Learn which candidates are worth the expensive evaluation.
VarianceSampler¶
UCB over the recent reward variance of each task.
VerifiableReport¶
What policy_improve did: the per-round held-out verifiable reward curve (the trajectory-level signal RL trains on) and the before/after on held-out verifiable facts.
VerifiedSeed¶
The archive's rule records that clear the trust bar, and the reasons the rest were excluded.
VerifierAgreement¶
Reject a commit whose gain the verifier claims and the oracle denies.
VerifierCurriculum¶
Select the tasks where the verifier disagrees most with the oracle.
VerifierProtocol¶
Four methods, from grep 'self\.verifier\.' agentdescent/aggregator.py.
VersionVector¶
Dict[str, int] — artifact id to version.
WorkspaceProvider¶
Provisions LocalWorkspaceSandbox -- mkdtemp, plus a lease file.
admit¶
Compose the ring statuses into one verdict.
admit_record¶
Admit an archived rule using its own exam stamp plus any ring statuses.
alerts¶
Human-readable alert conditions; empty means no alert.
apply_patch¶
Apply a patch_source diff back to a source. ValueError when the patch does not apply cleanly -- a patch whose context does not match source is refused, never half-applied. Untouched lines outside a hunk's context are preserved, so a middle-of-file edit keeps the rest of the file.
async_meta_population_run¶
meta_population_run with the islands run concurrently.
audited_meta_validate¶
meta_validate, with a probability sample of pairs sent to the oracle.
backends¶
Agentic backends -- a base agent that navigates documents with tools, not just maps a prompt to text.
baselines¶
The control every efficiency number in this repository is missing.
budgeted_completion¶
Wrap a max_tokens -> Completion factory into an adaptive Completion.
build_manifest¶
Hash a spec, its data files/directories, model config and code version.
canary¶
Compare candidate vs incumbent per task and recommend promote/hold/rollback.
chain_audit¶
Trace one archived adoption through its evidence rings.
compose_run¶
Run the harness(es) for a domain, record the outcomes, pick a winner.
contamination_overlap¶
Fraction of validation n-grams that also occur in the training text.
cost_adjusted_reward¶
quality - lam * (cost / baseline), clipped to [0, 1].
curiosity_weight¶
How much replay weight this example should carry -- failure-weighted: high while the artifact is still failing it (mean far below the ceiling), falling as it improves, floor once mastered. floor keeps a mastered example replayable without dominating the still-curious past. (This is a failure/need signal, not learning-progress: a permanently stuck example stays heavily weighted until it is solved.)
curious_replay_evolve¶
Evolve the artifact on its own past, weighted by curiosity.
curriculum_meta_run¶
Evolve a decision slot against a problem distribution the meta loop generates.
dataloader¶
Dependency-free dataset loading -- the data layer for examples/experiments.
diagnose_payload¶
Classify why a run ended where it did.
diff_manifest¶
Field-level differences between two manifests (empty when identical).
distill¶
The rationales the artifact will carry: deduped rules from the rollouts, near-miss rationales included -- they are the artifact's own notes about what it still needs. Dedup is on the exact rationale text, so the output value stays semantic content (-1 and 1, A and a are different rules, not formatting to normalise away).
estimate_campaign¶
Price a campaign without running it (one composed arm per session).
estimate_tournament¶
The per-arm plan estimate without running anything.
evaluate¶
Compare verifier with oracle on a deterministic sample.
evaluate_source¶
Run the candidate against the tests and the benchmark.
evolve_fast_rule¶
Evolve a selection priority rule on quality-per-cost, beat the seed.
exam_archive¶
Re-validate the archive's adopted rules against an independent oracle.
extract_rationale¶
One rollout's rationale as a rule the skill can carry.
failed_rollouts¶
The recorded rollouts that failed, up to max_failures.
find_leakage¶
The protected strings that appear in text (normalised match).
gate_source¶
The candidate's gate: exactly one function of no arguments other than what its signature declares, built only from safe syntax. Imports, dunder access and the calls that reach the interpreter are refused -- the same walk compile_policy_source runs over every self-edit.
harness¶
The least a harness can be: a name and a (tasks, repo) -> result callable.
harness_leaderboard¶
harness_name -> {rewards, best} over the archive's recorded outcomes.
harness_score¶
The best measured reward this harness has recorded for the domain.
imagine_then_evolve¶
Evolve against the simulator, verify the result on the real problem.
improve_source¶
Evolve the source of one function under its own tests and benchmark.
is_significant¶
Whether the lower bound clears min_effect and the interval is usable.
joint_archive_rule¶
Write an adopted bundle to the archive as one meta-rule per slot.
joint_meta_run¶
Evolve several decision slots together, validate them jointly, decide.
joint_meta_validate¶
Attribute a joint change to its slots, and isolate the ones that hurt.
joint_problem¶
An inner evolve() whose candidate is the whole bundle.
layers_from_archive¶
One layer per decision slot, aggregated from the archive's rule records.
meta_population_run¶
Run a population of meta sessions from the archive's best rules, promote the winner.
meta_run_audited¶
meta_run with an audited validation: sample, oracle, persist, decide.
metrics_from_log¶
Aggregate any iterable log (a JSONL-backed log, list, or generator).
metrics_from_records¶
Aggregate decision records.
outcome_features¶
The features a judge learns quality from: mean best-so-far, the final reward, and a constant intercept term. Sweep counts / rollouts are deliberately dropped: raw counts dwarf a [0, 1] quality in a linear fit and had the old judge saturating its clip at 1.0 for every outcome. The judge must learn quality from the curve's shape, not be handed the answer.
paired_gain¶
Per-task after - before over the keys present in both sides.
patch_source¶
A unified diff between two sources -- the patch a human or a reviewer sees, and what an apply can reverse.
policy_improve¶
Evolve a decision slot on verifiable reward and report the curve.
portfolio_evolve¶
Evolve the reallocation rule on the archive's (or the caller's) windows.
portfolio_problem¶
A Problem over one layer window.
portfolio_report¶
Score the layers by return-per-cost and reallocate the next window.
priority_grid_problem¶
A Problem over the priority grid.
problem_from_tasks¶
A runnable inner Problem over exactly these tasks.
rank_slots¶
The k slots to evolve next: paying slots first, plus exploration.
rationale_evolve¶
Evolve the skill, roll out, distill its own successes back into it.
reallocation_spec¶
The evolvable spec: the four numbers as a ParamSlot.
record_harness¶
Record one harness outcome for a domain. Idempotent by run_id.
recursive_self_improve¶
Run self_improve and portfolio_evolve alternately.
red_team¶
Attack value by mutating the seed tasks outward and scoring each mutation. Returns the (task, score) failures -- the OOD region where the value does not generalise. A deterministic attacker: the same mutations in the same order, so the attack is reproducible.
red_team_evolve¶
Evolve, attack the result, retrain on the failures it found.
rollback_plan¶
Files to restore/delete to return current to previous.
rsi_report¶
The archive's per-slot adoption history and a longitudinal verdict.
rule_id¶
Content-address a proposal so identical proposals dedupe automatically.
rule_tournament¶
Run a round-robin between the rules, rated by Elo.
run_campaign¶
Run up to sessions sessions, keeping the best artifact.
run_controller¶
Run sessions until budget, a kill switch, an error or stagnation.
run_guarded¶
Run one spec with an injected acceptance policy and optional records.
run_tournament¶
Run every (algorithm, seed) arm and return the measured ranking.
safety_report¶
Check whether the archive is safe to keep seeding from.
search_evolve¶
Search the state tree for the best value under a fixed budget.
select_harness¶
Route a domain to the harness the archive's evidence says wins there.
self_improve¶
Run the closed loop for config.ticks ticks.
self_rewarding_meta_run¶
Evolve a decision slot with a judge that improves against truth.
simulator_problem¶
A Problem that runs the simulator.
simulator_score¶
The simulator against the real problem: how much the dream is worth.
snapshot¶
Compose the archive's per-slot, per-port and health evidence.
society_evolve¶
Evolve the society topology on correctness-minus-cost.
society_problem¶
A Problem over multi-step tasks.
society_spec¶
The evolvable spec: the value is a gated society() source compiled into a SocietyGraph. SOCIETY_SEED is the single-agent baseline -- the topology the search is asked to beat.
source_ops¶
The deterministic cost of a candidate rule: arithmetic/call nodes in its source, counted from the AST. An attributable unit of work -- evaluating a rule costs what its expression tree costs, and counting it from the source is reproducible where wallclock is not.
to_prometheus¶
A deterministic Prometheus text exposition (no timestamps).
tournament_promote¶
Run the tournament and promote the winner into the archive.
trajectory_challenger¶
A MetaProblemChallenger that mines the archive's recorded failures.
verifiable_problem¶
Wrap a (value, task) -> output problem so the outcome's reward is verifiable: 1.0 on exact match with gold, 0.0 otherwise, with the output and gold carried in detail for verifiable_reward.
verifiable_reward¶
The RL signal: 1.0 when the outcome's output exactly matches its gold, 0.0 otherwise. A rule that cannot be checked is not rewarded -- the DeepSeek-R1 verifiability contract.
verified_rules¶
The archive's seedable rule records, gated by trust.
verify_manifest¶
Rebuild the manifest from current inputs; return changed field names.
windows_from_archive¶
Train/validation layer windows from the archive's rule records.