fix(copilot): use bounded default for agent_mode_max_iterations

Change fixer default from -1 (infinite) to 10 (bounded) for safety.
Update guide to let LLM choose iteration count based on task complexity:
1 for single-step, 3-10 for multi-step, -1 for open-ended orchestration.
This commit is contained in:
Zamil Majdy
2026-03-17 00:17:56 +07:00
parent 59f05ed23c
commit 2ddddc0257
3 changed files with 17 additions and 14 deletions

View File

@@ -150,10 +150,13 @@ call in a loop until the task is complete:
1. Create a `SmartDecisionMakerBlock` node
(ID: `3b191d9f-356f-482d-8238-ba04b6d18381`)
2. Set `input_default`:
- `agent_mode_max_iterations`: `-1` (infinite loop — AI calls tools until
done) or a positive number for bounded iterations. Do NOT use `0`
(traditional mode) — it requires complex external conversation-history
loop wiring that the agent generator does not produce.
- `agent_mode_max_iterations`: Choose based on task complexity:
- `1` for single-step tool calls (AI picks one tool, calls it, done)
- `3``10` for multi-step tasks (AI calls tools iteratively)
- `-1` for open-ended orchestration (AI loops until it decides it's done)
Do NOT use `0` (traditional mode) — it requires complex external
conversation-history loop wiring that the agent generator does not
produce.
- `conversation_compaction`: `true` (recommended to avoid context overflow)
- Optional: `sys_prompt` for extra LLM context about how to orchestrate
3. Wire the `prompt` input from an `AgentInputBlock` (the user's task)
@@ -169,7 +172,7 @@ call in a loop until the task is complete:
**Example — Orchestrator calling two sub-agents:**
- Node 1: `AgentInputBlock` (input_default: `{"name": "task"}`)
- Node 2: `SmartDecisionMakerBlock` (input_default:
`{"agent_mode_max_iterations": -1, "conversation_compaction": true}`)
`{"agent_mode_max_iterations": 10, "conversation_compaction": true}`)
- Node 3: `AgentExecutorBlock` (sub-agent A — set `graph_id`, `graph_version`,
`input_schema`, `output_schema` from library agent)
- Node 4: `AgentExecutorBlock` (sub-agent B — same pattern)

View File

@@ -33,7 +33,7 @@ _TEXT_REPLACE_BLOCK_ID = "7e7c87ab-3469-4bcc-9abe-67705091b713"
# Defaults applied to SmartDecisionMakerBlock nodes by the fixer.
_SDM_DEFAULTS: dict[str, object] = {
"agent_mode_max_iterations": -1,
"agent_mode_max_iterations": 10,
"conversation_compaction": True,
"retry": 3,
"multiple_tool_calls": False,

View File

@@ -136,7 +136,7 @@ class TestFixSmartDecisionMakerBlocks:
result = fixer.fix_smart_decision_maker_blocks(agent)
defaults = result["nodes"][0]["input_default"]
assert defaults["agent_mode_max_iterations"] == -1
assert defaults["agent_mode_max_iterations"] == 10
assert defaults["conversation_compaction"] is True
assert defaults["retry"] == 3
assert defaults["multiple_tool_calls"] is False
@@ -220,7 +220,7 @@ class TestFixSmartDecisionMakerBlocks:
result = fixer.fix_smart_decision_maker_blocks(agent)
assert "input_default" in result["nodes"][0]
assert result["nodes"][0]["input_default"]["agent_mode_max_iterations"] == -1
assert result["nodes"][0]["input_default"]["agent_mode_max_iterations"] == 10
def test_handles_none_input_default(self):
"""Node with input_default set to None gets a dict created."""
@@ -236,7 +236,7 @@ class TestFixSmartDecisionMakerBlocks:
result = fixer.fix_smart_decision_maker_blocks(agent)
assert isinstance(result["nodes"][0]["input_default"], dict)
assert result["nodes"][0]["input_default"]["agent_mode_max_iterations"] == -1
assert result["nodes"][0]["input_default"]["agent_mode_max_iterations"] == 10
def test_treats_none_values_as_missing(self):
"""Explicit None values are overwritten with defaults."""
@@ -258,7 +258,7 @@ class TestFixSmartDecisionMakerBlocks:
result = fixer.fix_smart_decision_maker_blocks(agent)
defaults = result["nodes"][0]["input_default"]
assert defaults["agent_mode_max_iterations"] == -1 # None → default
assert defaults["agent_mode_max_iterations"] == 10 # None → default
assert defaults["conversation_compaction"] is True # None → default
assert defaults["retry"] == 3 # kept
assert defaults["multiple_tool_calls"] is False # kept
@@ -280,7 +280,7 @@ class TestFixSmartDecisionMakerBlocks:
# First node: 3 defaults filled (agent_mode was already set)
assert result["nodes"][0]["input_default"]["agent_mode_max_iterations"] == 3
# Second node: all 4 defaults filled
assert result["nodes"][1]["input_default"]["agent_mode_max_iterations"] == -1
assert result["nodes"][1]["input_default"]["agent_mode_max_iterations"] == 10
assert len(fixer.fixes_applied) == 7 # 3 + 4
def test_registered_in_apply_all_fixes(self):
@@ -294,7 +294,7 @@ class TestFixSmartDecisionMakerBlocks:
result = fixer.apply_all_fixes(agent)
defaults = result["nodes"][0]["input_default"]
assert defaults["agent_mode_max_iterations"] == -1
assert defaults["agent_mode_max_iterations"] == 10
assert any("SmartDecisionMakerBlock" in fix for fix in fixer.fixes_applied)
@@ -528,7 +528,7 @@ class TestSmartDecisionMakerE2EPipeline:
n for n in fixed["nodes"] if n["block_id"] == SMART_DECISION_MAKER_BLOCK_ID
]
assert len(sdm_nodes) == 1
assert sdm_nodes[0]["input_default"]["agent_mode_max_iterations"] == -1
assert sdm_nodes[0]["input_default"]["agent_mode_max_iterations"] == 10
assert sdm_nodes[0]["input_default"]["conversation_compaction"] is True
# Validate (standalone SDM check)
@@ -557,7 +557,7 @@ class TestSmartDecisionMakerE2EPipeline:
# Fix fills defaults fine
fixer = AgentFixer()
fixed = fixer.apply_all_fixes(agent)
assert fixed["nodes"][1]["input_default"]["agent_mode_max_iterations"] == -1
assert fixed["nodes"][1]["input_default"]["agent_mode_max_iterations"] == 10
# Validate catches missing tools
validator = AgentValidator()