Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The framework is provider-agnostic (any LLM via LiteLLM), configuration-driven (

**Agent Orchestration**

Define agents with roles, models, and tools. The engine handles task decomposition, routing, execution loops (ReAct, Plan-and-Execute, auto-selection by complexity), crash recovery (checkpoint resume), and multi-agent coordination.
Define agents with roles, models, and tools. The engine handles task decomposition, routing, execution loops (ReAct, Plan-and-Execute, Hybrid, auto-selection by complexity), crash recovery (checkpoint resume), and multi-agent coordination.

</td>
<td width="33%">
Expand Down
8 changes: 7 additions & 1 deletion docs/design/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ composes the execution loop with prompt construction, context management, tool
invocation, and cost tracking into a single `run()` call. When an
`auto_loop_config` is provided (mutually exclusive with `execution_loop`),
the engine dynamically selects the loop per task via `_resolve_loop()`.
Optional `plan_execute_config`, `hybrid_loop_config`, and
`compaction_callback` are forwarded to the auto-selected loop so it
receives the same configuration as a statically configured loop.

The engine also exposes an optional ``coordinate()`` method that delegates to a
``MultiAgentCoordinator`` when one is configured (see :doc:`coordination`).
Expand Down Expand Up @@ -486,7 +489,10 @@ async run(
Budget-aware downgrade: hybrid is downgraded to plan_execute when
utilization >= threshold. Optional hybrid fallback applies when
`hybrid_fallback` is configured. When no auto config is set, uses
the statically configured loop.
the statically configured loop. The auto-selected loop receives the
engine's `compaction_callback`, `plan_execute_config` (for
plan-execute), and `hybrid_loop_config` (for hybrid), along with the
approval gate and stagnation detector.
9. **Delegate to loop** -- calls `ExecutionLoop.execute()` with context,
provider, tool invoker, budget checker, and completion config. If
`timeout_seconds` is set, wraps the call in `asyncio.wait`; on expiry
Expand Down
52 changes: 42 additions & 10 deletions src/synthorg/engine/agent_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
from synthorg.budget.tracker import CostTracker
from synthorg.core.agent import AgentIdentity
from synthorg.core.task import Task
from synthorg.engine.compaction import CompactionCallback
from synthorg.engine.coordination.models import (
CoordinationContext,
CoordinationResult,
Expand All @@ -107,6 +108,7 @@
ExecutionLoop,
ShutdownChecker,
)
from synthorg.engine.plan_models import PlanExecuteConfig
from synthorg.engine.stagnation.protocol import StagnationDetector
from synthorg.engine.task_engine import TaskEngine
from synthorg.persistence.repositories import (
Expand Down Expand Up @@ -152,18 +154,40 @@ class AgentEngine:
enhanced in-flight budget checking.
security_config: Optional security subsystem configuration.
approval_store: Optional approval queue store.
parked_context_repo: Optional repository for parking
execution contexts during approval escalation.
task_engine: Optional centralized task engine for real-time
status sync (incremental transitions at each lifecycle
point, best-effort).
checkpoint_repo: Optional checkpoint repository for
persisting execution state at turn boundaries.
Must be paired with ``heartbeat_repo``.
heartbeat_repo: Optional heartbeat repository for
crash detection during execution. Must be paired
with ``checkpoint_repo``.
checkpoint_config: Checkpoint tuning (interval, max size).
Defaults to ``CheckpointConfig()``.
coordinator: Optional multi-agent coordinator for delegated
coordination via :meth:`coordinate`.
stagnation_detector: Optional detector for repetitive
tool-call patterns. Wired into the execution loop
when using auto-selection or the default loop.
auto_loop_config: Optional auto-loop selection configuration.
Selects the execution loop per-task based on complexity
and budget state. Mutually exclusive with
``execution_loop``.
hybrid_loop_config: Optional configuration for the hybrid
plan+ReAct loop. Passed to ``build_execution_loop``
when auto-selection picks ``"hybrid"``.
compaction_callback: Optional async callback invoked at turn
boundaries to compress older conversation turns. Passed
to the execution loop (both static default and
auto-selected). When ``execution_loop`` is provided
directly, the caller is responsible for wiring this
callback into the loop.
plan_execute_config: Optional configuration for the
plan-execute loop. Passed to ``build_execution_loop``
when auto-selection picks ``"plan_execute"``.
"""

def __init__( # noqa: PLR0913
Expand All @@ -188,6 +212,8 @@ def __init__( # noqa: PLR0913
stagnation_detector: StagnationDetector | None = None,
auto_loop_config: AutoLoopConfig | None = None,
hybrid_loop_config: HybridLoopConfig | None = None,
compaction_callback: CompactionCallback | None = None,
plan_execute_config: PlanExecuteConfig | None = None,
) -> None:
if execution_loop is not None and auto_loop_config is not None:
msg = "execution_loop and auto_loop_config are mutually exclusive"
Expand All @@ -202,17 +228,22 @@ def __init__( # noqa: PLR0913
self._stagnation_detector = stagnation_detector
self._auto_loop_config = auto_loop_config
self._hybrid_loop_config = hybrid_loop_config
self._compaction_callback = compaction_callback
self._plan_execute_config = plan_execute_config
self._approval_gate = self._make_approval_gate()
if execution_loop is not None and (
self._approval_gate is not None or self._stagnation_detector is not None
self._approval_gate is not None
or self._stagnation_detector is not None
or self._compaction_callback is not None
):
logger.warning(
APPROVAL_GATE_LOOP_WIRING_WARNING,
note=(
"execution_loop provided externally — approval_gate "
"and stagnation_detector will NOT be wired "
"automatically. Configure the loop with "
"approval_gate= and stagnation_detector= explicitly."
"execution_loop provided externally -- approval_gate, "
"stagnation_detector, and compaction_callback will NOT "
"be wired automatically. Configure the loop with "
"approval_gate=, stagnation_detector=, and "
"compaction_callback= explicitly."
),
)
self._loop: ExecutionLoop = execution_loop or self._make_default_loop()
Expand Down Expand Up @@ -259,6 +290,9 @@ def __init__( # noqa: PLR0913
has_cost_tracker=self._cost_tracker is not None,
has_budget_enforcer=self._budget_enforcer is not None,
has_coordinator=self._coordinator is not None,
has_compaction_callback=self._compaction_callback is not None,
has_plan_execute_config=self._plan_execute_config is not None,
has_hybrid_loop_config=self._hybrid_loop_config is not None,
)

@property
Expand Down Expand Up @@ -1002,6 +1036,7 @@ def _make_default_loop(self) -> ReactLoop:
return ReactLoop(
approval_gate=self._approval_gate,
stagnation_detector=self._stagnation_detector,
compaction_callback=self._compaction_callback,
)

async def _resolve_loop(
Expand All @@ -1015,11 +1050,6 @@ async def _resolve_loop(
When ``auto_loop_config`` is set, selects the loop based on
task complexity and optional budget state. Otherwise returns
the statically configured loop (``self._loop``).

Note: auto-selected loops use default ``PlanExecuteConfig``
and do not receive a compaction callback. Provide an
``execution_loop`` directly for custom plan-execute config
or compaction.
"""
if self._auto_loop_config is None:
return self._loop
Expand Down Expand Up @@ -1070,6 +1100,8 @@ async def _resolve_loop(
loop_type,
approval_gate=self._approval_gate,
stagnation_detector=self._stagnation_detector,
compaction_callback=self._compaction_callback,
plan_execute_config=self._plan_execute_config,
hybrid_loop_config=self._hybrid_loop_config,
)

Expand Down
9 changes: 9 additions & 0 deletions src/synthorg/engine/checkpoint/resume.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from synthorg.engine.checkpoint.callback_factory import make_checkpoint_callback
from synthorg.engine.checkpoint.models import CheckpointConfig # noqa: TC001
from synthorg.engine.context import AgentContext
from synthorg.engine.hybrid_loop import HybridLoop
from synthorg.engine.plan_execute_loop import PlanExecuteLoop
from synthorg.engine.react_loop import ReactLoop
from synthorg.observability import get_logger
Expand Down Expand Up @@ -136,6 +137,14 @@ def make_loop_with_callback( # noqa: PLR0913
stagnation_detector=loop.stagnation_detector,
compaction_callback=loop.compaction_callback,
)
if isinstance(loop, HybridLoop):
return HybridLoop(
config=loop.config,
checkpoint_callback=callback,
approval_gate=loop.approval_gate,
stagnation_detector=loop.stagnation_detector,
compaction_callback=loop.compaction_callback,
)
logger.warning(
CHECKPOINT_UNSUPPORTED_LOOP,
loop_type=type(loop).__name__,
Expand Down
Loading
Loading