Skip to content

Commit 9bd9354

Browse files
authored
[improvement][loop-comments][compact every multi-line comment to one line] (kyegomez#2065)
autonomous_loop.py carried 34 multi-line comment blocks, the longest nine lines. House style is one line: anything needing a paragraph belongs in the docstring, and these keep arriving in PRs and keep getting removed. Comments only - the diff contains no code changes. One block was not merely reformatted. "Tools the loop itself depends on ... never deferred" sat directly above PREWARM_TOOL_LIMIT, but it describes ALWAYS_LOADED_TOOLS five lines further down, which had no comment at all. It now sits on the constant it documents.
1 parent d7c6f1e commit 9bd9354

1 file changed

Lines changed: 34 additions & 112 deletions

File tree

swarms/agents/autonomous_loop.py

Lines changed: 34 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,13 @@ def _format_tool_error(function_name: str, error: Exception) -> str:
7474
)
7575

7676

77-
# Tools the loop itself depends on. These are never deferred - an agent that
78-
# has to search for its own `subtask_done` cannot finish a subtask.
79-
# How many tools a plan may pre-load in one go. Enough to cover a typical plan
80-
# without pulling in the whole catalog and undoing the saving.
77+
# Enough for a typical plan without pulling in the whole catalog.
8178
PREWARM_TOOL_LIMIT = 8
8279

8380
# Pre-warm matches must score at least this fraction of the best match.
8481
PREWARM_MIN_SCORE_RATIO = 0.6
8582

83+
# Never deferred: searching for subtask_done would stall the loop.
8684
ALWAYS_LOADED_TOOLS = frozenset(
8785
{
8886
"create_plan",
@@ -110,14 +108,9 @@ class AutonomousAgentLoop:
110108

111109
def __init__(self, agent: Any):
112110
self.agent = agent
113-
# The real conversation body sent to the model: user turns, assistant
114-
# turns carrying `tool_calls`, and `{"role": "tool", ...}` results.
115-
# `short_memory` is kept in sync alongside it because persistence,
116-
# output formatting and the final summary all read from there.
111+
# The real body sent to the model; short_memory mirrors it.
117112
self._transcript = Transcript()
118-
# The handoff block this loop last appended to the agent's
119-
# system_prompt. Held so a later run can remove it before appending the
120-
# current one, instead of stacking a fresh copy every run.
113+
# Removed before the next append, so runs do not stack copies.
121114
self._applied_handoff_block: Optional[str] = None
122115

123116
def _say_user(self, content: str, mirror: bool = True) -> None:
@@ -225,8 +218,7 @@ def _run_autonomous_loop(
225218
"""
226219
try:
227220

228-
# Reset autonomous loop state. The transcript is cleared before
229-
# the task is seeded, or the opening turn would be discarded.
221+
# Cleared before seeding, or the opening turn is lost.
230222
self._transcript = Transcript()
231223
self.agent.autonomous_subtasks = []
232224
self.agent.current_subtask_index = 0
@@ -259,14 +251,7 @@ def _run_autonomous_loop(
259251
f"Filtered to {len(planning_tools)} tools: {[t.get('function', {}).get('name', '') for t in planning_tools]}"
260252
)
261253

262-
# The `think` tool is opt-in via Agent(think_tool=True). It costs a
263-
# full round-trip to produce reasoning the model could emit inline
264-
# alongside its actions, so it is off unless asked for.
265-
#
266-
# This replaces an earlier `thinking_tokens is not None` check.
267-
# `thinking_tokens` defaults to 1024 rather than None, so that test
268-
# was always true and silently stripped `think` for every agent -
269-
# the intent was to drop it only when extended thinking was on.
254+
# Opt-in; the old thinking_tokens check was always true.
270255
if not getattr(self.agent, "think_tool", False):
271256
planning_tools = [
272257
t
@@ -284,9 +269,7 @@ def _run_autonomous_loop(
284269
if self.agent.tools_list_dictionary is None:
285270
self.agent.tools_list_dictionary = []
286271

287-
# With dynamic_tools, only the control tools are always present.
288-
# The rest of the loop's tools - file, shell, grep, sub-agents -
289-
# go into the catalog and are loaded on demand via tool_search.
272+
# Only control tools stay; the rest load via tool_search.
290273
if self.agent.dynamic_tools:
291274
control = [
292275
t
@@ -328,15 +311,7 @@ def _run_autonomous_loop(
328311
self.agent.tools_list_dictionary.append(tool)
329312
existing_tool_names.add(tool_name)
330313

331-
# Add handoff prompt to system prompt.
332-
#
333-
# This runs on every run(), so a bare append accumulated a
334-
# fresh copy each time and grew the prompt without bound on a
335-
# reused agent. The block this loop last applied is remembered
336-
# and removed before the current one goes on, which keeps the
337-
# prompt the same size across runs while still refreshing it
338-
# when the registry changes — a plain "already present" guard
339-
# would pin the first registry's text forever.
314+
# Removed first, so a changed roster cannot go stale.
340315
agent_registry = self.agent._get_agent_registry()
341316
if agent_registry:
342317
handoff_prompt = get_handoffs_prompt(
@@ -355,8 +330,7 @@ def _run_autonomous_loop(
355330
)
356331
)
357332

358-
# Only append when it is not already there, so a prompt
359-
# that carries the text for another reason is left alone.
333+
# Left alone if present for another reason.
360334
if handoff_block not in self.agent.system_prompt:
361335
self.agent.system_prompt += handoff_block
362336

@@ -558,8 +532,7 @@ def _run_autonomous_loop(
558532
plan_created = True
559533
break
560534

561-
# Every tool_call in the assistant turn must be answered
562-
# before the next request, whether or not the plan landed.
535+
# Answer every tool_call before the next request.
563536
self._flush_tool_results(
564537
planning_calls, planning_results
565538
)
@@ -582,8 +555,7 @@ def _run_autonomous_loop(
582555
"Failed to create plan after maximum attempts"
583556
)
584557

585-
# Integrate user tools after planning phase. With dynamic_tools
586-
# they are already in the catalog, reachable through tool_search.
558+
# Already in the catalog when dynamic_tools is on.
587559
if (
588560
exists(self.agent.tools)
589561
and not self.agent.dynamic_tools
@@ -685,15 +657,10 @@ def _run_autonomous_loop(
685657
max_subtask_loops = MAX_SUBTASK_LOOPS
686658
subtask_done = False
687659

688-
# Counts CONSECUTIVE think calls across this subtask's
689-
# iterations. It is reset here, once per subtask, and again by
690-
# any non-think tool call below - not once per iteration, which
691-
# would only ever catch repeats inside a single response.
660+
# Consecutive across the subtask, not one response.
692661
self.agent.think_call_count = 0
693662

694-
# Add the execution prompt ONCE before the inner loop so the model
695-
# doesn't see duplicate copies of it on subsequent iterations and
696-
# mistakenly conclude "this task has been run before."
663+
# Once only, or the model reads duplicates as a rerun.
697664
execution_prompt = get_execution_prompt(
698665
subtask_id,
699666
subtask_desc,
@@ -726,17 +693,14 @@ def _run_autonomous_loop(
726693
content=response,
727694
)
728695

729-
# Record the model's turn, then answer every tool call
730-
# it made before the next request goes out.
696+
# Answer every call before the next request.
731697
turn_calls = self._record_assistant(response)
732698
turn_results: Dict[str, Any] = {}
733699

734700
# Handle tool calls
735701
if isinstance(response, list):
736702
regular_tool_calls = []
737-
# complete_task sets this instead of returning
738-
# mid-loop, so tool calls batched after it are not
739-
# dropped.
703+
# Set, not returned, so later calls run.
740704
task_complete = False
741705

742706
for tool_call in response:
@@ -760,9 +724,7 @@ def _run_autonomous_loop(
760724
json.JSONDecodeError,
761725
TypeError,
762726
) as parse_error:
763-
# Report the malformed payload back to
764-
# the model instead of aborting the
765-
# whole iteration on one bad call.
727+
# Report back, do not abort.
766728
self.agent.short_memory.add(
767729
role="Tool Executor",
768730
content=_format_tool_error(
@@ -781,9 +743,7 @@ def _run_autonomous_loop(
781743
function_name
782744
in planning_tool_handlers
783745
):
784-
# Set when the handler raises, so a
785-
# failed call is not mistaken for a
786-
# completed subtask or a finished task.
746+
# A raise is not a completion.
787747
tool_failed = False
788748

789749
# Special handling for handoff_task tool
@@ -816,9 +776,7 @@ def _run_autonomous_loop(
816776
tool_error,
817777
)
818778
else:
819-
# Only pre-visualize tools that won't be shown again
820-
# with their result (subtask_done / complete_task are
821-
# visualized post-execution so skip the pre call).
779+
# Shown post-execution.
822780
if function_name not in (
823781
"subtask_done",
824782
"complete_task",
@@ -852,9 +810,7 @@ def _run_autonomous_loop(
852810
tool_call.get("id", "")
853811
] = result
854812

855-
# Any tool that is not `think` breaks
856-
# the streak, which is what makes the
857-
# limit a *consecutive* one.
813+
# Non-think breaks the streak.
858814
if function_name != "think":
859815
self.agent.think_call_count = (
860816
0
@@ -880,8 +836,7 @@ def _run_autonomous_loop(
880836
result,
881837
)
882838

883-
# Check if subtask is done. A failed
884-
# handler does not complete anything.
839+
# A failure completes nothing.
885840
if (
886841
function_name
887842
== "subtask_done"
@@ -910,9 +865,7 @@ def _run_autonomous_loop(
910865
title=f"Subtask {status.title()}: {subtask_id}",
911866
)
912867

913-
# Check if main task is complete. The
914-
# return is deferred until every tool
915-
# call in this response has run.
868+
# Deferred until calls finish.
916869
if (
917870
function_name
918871
== "complete_task"
@@ -925,11 +878,7 @@ def _run_autonomous_loop(
925878
tool_call
926879
)
927880

928-
# Handle all regular tools together
929-
# MCP tools are served by the MCP manager, not by
930-
# tool_struct, which resolves against self.tools
931-
# only. Split them out first or every MCP call
932-
# raises ToolNotFoundError.
881+
# MCP resolves elsewhere; split first.
933882
if regular_tool_calls:
934883
(
935884
mcp_calls,
@@ -1178,9 +1127,7 @@ def _run_autonomous_loop(
11781127
logger.warning(
11791128
f"Too many consecutive think calls ({self.agent.think_call_count}). Forcing action."
11801129
)
1181-
# Force action. The nudge goes into the real
1182-
# transcript, not just short_memory, or the model
1183-
# never sees it on the next request.
1130+
# Into the transcript, or it is unseen.
11841131
nudge = (
11851132
"You have called `think` "
11861133
f"{self.agent.think_call_count} times in a row "
@@ -1193,18 +1140,15 @@ def _run_autonomous_loop(
11931140
)
11941141
self._transcript.append_user(nudge)
11951142

1196-
# Reset so the nudge gets a fair chance to work
1197-
# before firing again on the very next iteration.
1143+
# Give the nudge a chance before refiring.
11981144
self.agent.think_call_count = 0
11991145

12001146
except Exception as e:
12011147
if self.agent.verbose:
12021148
logger.error(
12031149
f"Error in subtask execution loop: {e}"
12041150
)
1205-
# Record the failure in the conversation. Without this
1206-
# the next iteration rebuilds an identical prompt and
1207-
# the model repeats whatever just failed.
1151+
# Without this the next prompt is identical.
12081152
self.agent.short_memory.add(
12091153
role="Tool Executor",
12101154
content=(
@@ -1215,13 +1159,7 @@ def _run_autonomous_loop(
12151159
)
12161160

12171161
if not subtask_done:
1218-
# A subtask that burned its whole iteration budget without
1219-
# finishing is recorded as failed, not left pending. Left
1220-
# pending it stays eligible, so the outer loop re-selects it
1221-
# and re-runs the same doomed budget up to
1222-
# MAX_SUBTASK_ITERATIONS times - 100 x 20 = 2000 LLM calls
1223-
# for one stuck subtask. Failing it terminates the run,
1224-
# cascades `skipped` to its dependents, and reports honestly.
1162+
# Failed, not pending; pending would re-run it.
12251163
reason = (
12261164
f"Exhausted its {max_subtask_loops}-iteration budget "
12271165
"without completing."
@@ -1345,17 +1283,13 @@ def _create_plan_tool(
13451283
incoming: Dict[str, Dict[str, Any]] = {}
13461284
incoming_order: List[str] = []
13471285
known_step_ids = {step.get("step_id", "") for step in steps}
1348-
# A revision may reference work that already finished but is not being
1349-
# restated, so those ids stay valid dependency targets.
1286+
# Finished work stays a valid dependency target.
13501287
known_step_ids |= set(existing)
13511288

13521289
for step in steps:
13531290
step_id = step.get("step_id", "")
13541291

1355-
# step_id values in `dependencies` are model-generated free text.
1356-
# A typo or hallucinated id used to satisfy the dependency check
1357-
# silently; now it is dropped, with a warning, so the plan stays
1358-
# runnable instead of deadlocking on a reference to nothing.
1292+
# Model-generated ids; drop bad ones, do not deadlock.
13591293
declared = step.get("dependencies", []) or []
13601294
dependencies = [
13611295
dep
@@ -1381,8 +1315,7 @@ def _create_plan_tool(
13811315
}
13821316
incoming_order.append(step_id)
13831317

1384-
# Merge, preserving the order the plan already had and appending
1385-
# genuinely new work at the end.
1318+
# Keep existing order, append new work at the end.
13861319
merged: List[Dict[str, Any]] = []
13871320
added, updated, removed, retained = [], [], [], []
13881321

@@ -1446,8 +1379,7 @@ def _create_plan_tool(
14461379
)
14471380
return message
14481381

1449-
# A revision reports what changed, not the whole plan, so the model
1450-
# can see the effect of its edit.
1382+
# Reports the change, not the whole plan.
14511383
diff_parts = []
14521384
if added:
14531385
diff_parts.append(f"added {added}")
@@ -1484,8 +1416,7 @@ def _mcp_tool_names(self) -> set:
14841416
if not getattr(agent, "mcp_enabled", False):
14851417
return set()
14861418

1487-
# Prefer the cache the dynamic loader already populated, so this does
1488-
# not add a network call per turn.
1419+
# Use the loader's cache to avoid a call per turn.
14891420
schemas = getattr(agent, "_mcp_schemas_cache", None)
14901421
if schemas is None:
14911422
try:
@@ -1582,10 +1513,7 @@ def _prewarm_tools_from_plan(
15821513
agent._tool_search_tool(
15831514
query=query,
15841515
max_results=PREWARM_TOOL_LIMIT,
1585-
# Speculative, so it demands stronger relevance than an explicit
1586-
# search. A whole plan as the query contains enough common words
1587-
# ("task", "data", "current") to give unrelated tools a nonzero
1588-
# score, which would load the catalog and undo the saving.
1516+
# Speculative, so relevance must beat an explicit search.
15891517
min_score_ratio=PREWARM_MIN_SCORE_RATIO,
15901518
)
15911519
return [
@@ -1791,17 +1719,11 @@ def _get_next_executable_subtask(
17911719
for dep in dependencies
17921720
]
17931721

1794-
# Only an actually completed dependency satisfies. A failed one
1795-
# cannot produce the output its dependents were planned around,
1796-
# and an unknown id (None) means the reference is unresolvable --
1797-
# neither should unblock execution.
1722+
# Only completed unblocks; failed and unknown do not.
17981723
if all(status == "completed" for status in statuses):
17991724
return subtask
18001725

1801-
# Dependencies that failed or were skipped can never complete, so
1802-
# this subtask is unreachable. Mark it skipped rather than leaving
1803-
# it pending forever, so the run terminates and the final summary
1804-
# can report what was not attempted.
1726+
# Unreachable: skip so the run can terminate.
18051727
blockers = [
18061728
dep
18071729
for dep, status in zip(dependencies, statuses)

0 commit comments

Comments
 (0)