feat(delegation): uncap max_spawn_depth (floor 1, no ceiling) (#39772)
* fix: respect disabled auto-compaction on context overflow Port from anomalyco/opencode#30749. When compression.enabled is false, NO automatic compaction trigger may fire. The proactive token-threshold paths (preflight + post-response should_compress gate) already honoured the setting, but the three provider-overflow recovery paths in the agent loop — long-context-tier 429, 413 payload-too-large, and context-overflow — called _compress_context() unconditionally, silently compressing and rotating the session against the user's explicit choice. Add a single guard at the top of the overflow-recovery dispatch: when compression is disabled and the error is one of those three overflow classes, surface a terminal error (compaction_disabled: True) telling the user to /compress manually, /new, switch to a larger-context model, or reduce attachments. Manual /compress (force=True) is unaffected — it never enters this loop. Tests: new TestOverflowWithCompactionDisabled (413 + 400 overflow don't compress when disabled; control case still compresses when enabled). Existing overflow-recovery tests updated to enable compaction explicitly (they verify the recovery fires); fixture defaults flipped to True to match production (compression.enabled defaults to True). * feat(delegation): uncap max_spawn_depth to match max_concurrent_children Removed the hard ceiling of 3 on delegation.max_spawn_depth. Depth now has a floor of 1 and no upper limit, mirroring max_concurrent_children. Cost (each level multiplies API spend) is the practical limiter, not a constant. - delegate_tool.py: drop _MAX_SPAWN_DEPTH_CAP, _get_max_spawn_depth() floors at 1 instead of clamping to [1,3]; depth-limit error string reworded - config.py / cli-config.yaml.example: doc comments say floor 1, no ceiling - docs (configuration, delegation, delegation-patterns): range 1-3 -> >=1 - tests: convert clamp-above-3 change-detector into a no-ceiling invariant, drop the _MAX_SPAWN_DEPTH_CAP==3 snapshot assert, fix warning-text assert
This commit is contained in:
+15
-11
@@ -134,7 +134,9 @@ MAX_DEPTH = 1 # flat by default: parent (0) -> child (1); grandchild rejected u
|
||||
# Configurable depth cap consulted by _get_max_spawn_depth; MAX_DEPTH
|
||||
# stays as the default fallback and is still the symbol tests import.
|
||||
_MIN_SPAWN_DEPTH = 1
|
||||
_MAX_SPAWN_DEPTH_CAP = 3
|
||||
# No upper ceiling on spawn depth — like max_concurrent_children, depth has a
|
||||
# floor of 1 and no ceiling. Deeper trees multiply API cost, so the default
|
||||
# stays flat (MAX_DEPTH = 1); raising the config knob is an explicit opt-in.
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -392,7 +394,7 @@ def _get_child_timeout() -> float:
|
||||
|
||||
|
||||
def _get_max_spawn_depth() -> int:
|
||||
"""Read delegation.max_spawn_depth from config, clamped to [1, 3].
|
||||
"""Read delegation.max_spawn_depth from config, floored at 1 (no ceiling).
|
||||
|
||||
depth 0 = parent agent. max_spawn_depth = N means agents at depths
|
||||
0..N-1 can spawn; depth N is the leaf floor. Default 1 is flat:
|
||||
@@ -400,9 +402,11 @@ def _get_max_spawn_depth() -> int:
|
||||
(blocked by this guard AND, for leaf children, by the delegation
|
||||
toolset strip in _strip_blocked_tools).
|
||||
|
||||
Raise to 2 or 3 to unlock nested orchestration. role="orchestrator"
|
||||
removes the toolset strip for depth-1 children when
|
||||
Raise to 2+ to unlock nested orchestration. role="orchestrator"
|
||||
removes the toolset strip for spawning children when
|
||||
max_spawn_depth >= 2, enabling them to spawn their own workers.
|
||||
Like max_concurrent_children, there is no upper ceiling — but each
|
||||
extra level multiplies API cost, so raise it deliberately.
|
||||
"""
|
||||
cfg = _load_config()
|
||||
val = cfg.get("max_spawn_depth")
|
||||
@@ -417,16 +421,15 @@ def _get_max_spawn_depth() -> int:
|
||||
MAX_DEPTH,
|
||||
)
|
||||
return MAX_DEPTH
|
||||
clamped = max(_MIN_SPAWN_DEPTH, min(_MAX_SPAWN_DEPTH_CAP, ival))
|
||||
if clamped != ival:
|
||||
floored = max(_MIN_SPAWN_DEPTH, ival)
|
||||
if floored != ival:
|
||||
logger.warning(
|
||||
"delegation.max_spawn_depth=%d out of range [%d, %d]; " "clamping to %d",
|
||||
"delegation.max_spawn_depth=%d below floor %d; using %d",
|
||||
ival,
|
||||
_MIN_SPAWN_DEPTH,
|
||||
_MAX_SPAWN_DEPTH_CAP,
|
||||
clamped,
|
||||
floored,
|
||||
)
|
||||
return clamped
|
||||
return floored
|
||||
|
||||
|
||||
def _get_orchestrator_enabled() -> bool:
|
||||
@@ -1982,7 +1985,8 @@ def delegate_task(
|
||||
f"Delegation depth limit reached (depth={depth}, "
|
||||
f"max_spawn_depth={max_spawn}). Raise "
|
||||
f"delegation.max_spawn_depth in config.yaml if deeper "
|
||||
f"nesting is required (cap: {_MAX_SPAWN_DEPTH_CAP})."
|
||||
f"nesting is required (no hard ceiling, but each level "
|
||||
f"multiplies API cost)."
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user