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.
336 public names across 55 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,
retired_workers: int = 0,
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,
cache_hits: int = 0,
cache_misses: 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
) -> 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
) -> 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,
stop_when: Optional[Callable[['RoundInfo'], bool]] = None,
verbose: bool = False,
usage: Optional[Usage] = None,
policies: Optional['Policies'] = None
) -> 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. |
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. |
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
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. |
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). |
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.
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_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
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]
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,
_lock: threading.Lock = <factory>
) -> None
| method | what it does |
|---|---|
estimated_cost(per_1m_prompt: float, per_1m_completion: float) -> float |
Cost at the given per-million-token prices (both provider-specific). |
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,
**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] = ()
) -> None
| method | what it does |
|---|---|
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).
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
) -> 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'
) -> 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>,
advantage: Optional[float] = None,
cost_tokens: int = 0,
cost_wallclock: float = 0.0
) -> 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
) -> 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. |
measure(items: List[Union['_Candidate', MergeReport]]) -> List[Union['_Candidate', MergeReport]] |
Phase 2 for a batch from begin_step. Off-thread safe. |
restore(state: dict) -> None |
Restore state written by checkpoint. |
step() -> 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,
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 (design doc, section 4.1).
| method | what it does |
|---|---|
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' |
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 = ''
) -> 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. |
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
) -> 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": "..."}. |
| method | what it does |
|---|---|
calibration_set() -> list |
Resolved CALIBRATION records for this verifier version. |
pending() -> list |
Audited units still waiting on truth. |
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
) -> 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] = ()
) -> 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. |
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(verifier_version: Optional[str] = None) -> List[AuditRecord] |
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 |
|
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, via the Cornish-Fisher expansion in 1/df.
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 = 1
) -> 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.
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
)
| 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. |
restore(state: dict) -> None |
Restore the archive written by checkpoint. |
step() -> 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
)
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.
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 while merges land; tighten when they do not.
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. |
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) -> None¶
Turn-level checkpoints of timed-out rollouts (partial rollout).
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,
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,
policies: Dict[str, Any] = <factory>,
agg_config: Dict[str, Any] = <factory>,
evolve: Dict[str, Any] = <factory>,
allow: Sequence[str] = (),
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,
task_sampler: Optional['TaskSampler'] = None,
on_round: Optional[Callable[[RoundInfo], None]] = None,
stop_when: Optional[Callable[[RoundInfo], bool]] = None,
verbose: bool = False,
usage: Optional[Usage] = None,
policies: Optional['Policies'] = 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. |
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. |
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. |
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.
AggregatorFactory¶
(ledger, verifier, audit, config, policy) -> AggregatorProtocol — how a custom optimizer is installed.
AppendRules¶
Accumulate a deduped list of rules/lessons (append-only, content-addressed).
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.
Completion¶
Callable[[str], str] — the one contract every model and agent satisfies.
ConflictPolicy¶
Which of a batch of mutually contradictory changes survive.
DefaultConflict¶
Drop contradicting diffs, keeping whichever scores better (PCGrad-style).
DefaultFusion¶
Build the union of complementary diffs and hand it to the gate.
EDIT_PROTOCOL¶
The multi-file proposal format a FileTree reflector is told to emit.
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).
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.
KeyedRules¶
One entry per category: competing proposals contradict and are resolved.
LAYOUTS¶
Where a runner writes the evolving tree inside a workspace (claude_skill, skill_library, claude_agent, dsh_skill, agents_skill, root).
LedgerFailure¶
The exception tuple a caller catches to treat any ledger problem as recoverable.
LedgerProtocol¶
Seven methods: four the aggregator calls, three 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.
MemoryCache¶
In-process, single-flight, counted.
MergeContext¶
Everything an AcceptancePolicy is allowed to look at.
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.
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.
Ref¶
A callable named rather than sent: "module:attribute" plus config.
RefError¶
A reference could not be resolved, and why -- never a bare ImportError.
Result¶
What one rollout produced, or why it did not.
RolloutSpec¶
One rollout, described completely enough to run somewhere else.
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.
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.
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.
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.
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.
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.
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.
dataloader¶
Dependency-free dataset loading -- the data layer for examples/experiments.
rule_id¶
Content-address a proposal so identical proposals dedupe automatically.