feat(sx): invariant D — cross-cycle extension ceilings; the last self-extend audit GAP closes - #321
Merged
Conversation
…-extend audit GAP closes The self-extend audit enforced invariants A, B and C on the live extender path and left D — "cross-cycle extender recursion depth is not capped" — as the primary deferred safety item before treating unattended multi-cycle self-extension as production-safe. Per-cycle caps (ReAct iterations, build/test budget) were always there; nothing bounded the extender ACROSS cycles. ExtensionCeiling (Nexo.BackgroundAgents.Extending) — three ceilings, each a refusal on its own: - MaxLineageDepth (default 1): recursion proper. ParentId hops below a human-authored root an extender may sit and still extend; roots and their direct children may, machine-spawned grandchildren may not. An unresolvable parent still counts as a hop. - MaxUnattendedCycles (default 8): runaway. Extend cycles since a human last armed the agent, then hold. - MaxCyclesPerHour (default 4): rate, trailing hour. Posture mirrors the certified loop's RecursionDiscipline: the environment (NEXO_EXTENSION_MAX_*) and an agent's own Parameters may only LOWER a ceiling, never raise it, so no configuration an agent can write extends its own authority (I-1). Raising a default is a code change. Enforced in BackgroundAgentRegistry at the same point as invariant C — after the mode gate, before ISelfExtendRunner.RunAsync. Refusals are visible: Warning in the agent log, Warn observation with stopped_reason=extension_ceiling, cycle telemetry. Only cycles that reach the runner consume budget. Ledgers (ExtensionLedger) live on the registry, NOT the agent instance, so re-registration — which an agent can do to itself via UpdateAgentConfigTool — cannot reset them; re-arm is RearmExtension(agentId) (operator surface, never a tool) or a restart, and it clears the unattended count but not the hour's rate. A host may register a stricter ExtensionCeiling in DI. Tests: the invariant D rejection test is un-skipped (was the last GAP marker in the suite); default ceiling bounds 12 back-to-back cycles at 4 with 8 observable refusals; unattended ceiling binds when rate does not; re-registration does not re-arm and RearmExtension does; lineage grandchild refused; env and Parameters only lower (int / JSON / string shapes); ledger window slides and re-arm does not forgive the rate. BackgroundAgents suite 486/486, 0 skipped. Audit doc: D → ENFORCED, trace and index updated; Configuration.md gains the three env rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the last open item in the self-extend safety audit.
docs/SELF-EXTEND-AUDIT.mdenforced invariants A, B, C on the live extender path and left D — "cross-cycle extender recursion depth is not capped" — as "the primary deferred safety item before treating unattended multi-cycle self-extension as production-safe." Its rejection test was the last[Fact(Skip = "GAP…")]in the whole suite (and, incidentally, the test the readiness gate tripped over in #317).Per-cycle caps (ReAct iterations, build/test budget) were always there; nothing bounded the extender across cycles.
ExtensionCeiling— three ceilings, each a refusal on its ownMaxLineageDepthParentIdhops below a human-authored root an extender may sit and still extend: roots and their direct children may, machine-spawned grandchildren may not. An unresolvable parent still counts as a hop.MaxUnattendedCyclesMaxCyclesPerHourPosture mirrors the certified loop's
RecursionDiscipline: the environment (NEXO_EXTENSION_MAX_LINEAGE_DEPTH/…_UNATTENDED_CYCLES/…_CYCLES_PER_HOUR) and an agent's ownParameters(MaxLineageDepth/MaxUnattendedCycles/MaxCyclesPerHour) may only lower a ceiling, never raise it — so no configuration an agent can write extends its own authority (I-1). Raising a default is a code change. A host may register a stricterExtensionCeilingin DI.Enforcement
In
BackgroundAgentRegistry, at the same point as invariant C — after the mode gate, beforeISelfExtendRunner.RunAsync. A refusal is visible: Warning in the agent log, Warn observation withstopped_reason=extension_ceiling, cycle telemetry. Only cycles that actually reach the runner consume budget (a Passive skip or approval denial does not).The one design decision that matters: an agent cannot re-arm itself. Ledgers (
ExtensionLedger) live on the registry, not the agent instance, so re-registration — which an agent can do to itself viaUpdateAgentConfigTool(registers asAuthored) — does not reset them. Re-arm isRearmExtension(agentId)(an operator surface that must never be registered as a tool) or a process restart, and it clears the unattended count but not the hour's rate. A CLI verb for re-arm is a follow-up on anapplication/*branch.Tests (
SelfExtendInvariantDRecursionCeilingTests, 10)RearmExtensiondoes; lineage grandchild refused; env andParametersonly lower (int / JSON / string shapes); ledger window slides and re-arm does not forgive the rate.Docs: audit D → ENFORCED (trace, verdict table, index updated; REORDER note now names the real remaining question — one self-extension path, not two);
Configuration.mdgains the three env rows.Test plan
Nexo.Tests.BackgroundAgents486/486 locally (net9.0), 0 skipped🤖 Generated with Claude Code