Session controller — when is another self-improvement session justified?¶
run_guarded runs one session. A product needs the loop around it: session
budget, call budget, kill switch, stagnation stop and a decision record per
session. run_controller is that loop, and it deliberately does not know what a
session is.
from agentdescent import ControllerConfig, run_controller
report = run_controller(
run_session=lambda index, seed: my_session(index, seed), # -> SessionOutcome
config=ControllerConfig(max_sessions=5, max_calls=500, patience=2,
kill_switch=switch, decision_log=log))
print(report.best_reward, report.stopped_reason, report.total_calls)
run_session(index, seed)returnsSessionOutcome(reward, calls, error, evidence). It can wrap a guarded run, a campaign step, a population generation or a remote job.- The first successful session establishes the baseline; later sessions must
beat the best to reset stagnation.
patiencenon-improving sessions stop the loop.best_rewardisNonewhen no session succeeded. - An errored or raised session is recorded, stops the loop, and never updates
the best -- even if its partial reward is high.
error=""counts as an error. - A kill switch is checked before every session (an engagement during a session is seen at the next check); a call budget is checked before and after a session, so an overshoot stops before another one starts.
callsmust be a non-negative integer andrewardfinite; arun_sessionthat returns anything other thanSessionOutcomebecomes an error record.kill_switchis any object withengaged()andreason();decision_logany object withrecord(kind=..., artifact_id=..., accepted=..., category=..., detail=..., evidence=..., cost=...). With a log, each session becomes akind="session"record with reward, improved flag, error and calls.stopped_reasonis a fixed code (sessions/budget/kill-switch/error/patience);reasonis the human-readable detail.
This is the scheduling boundary: engine/artifact application stays with the caller, while "another session or not" has one tested answer.