Skip to content
Merged
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ src/synthorg/
meeting/ # Meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator
config/ # YAML company config loading and validation
core/ # Shared domain models, base classes, and resilience config (RetryConfig, RateLimiterConfig)
engine/ # Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recovery), context budget management (context_budget.py, ContextBudgetIndicator, fill estimation, token estimation protocol in token_estimation.py), conversation compaction (compaction/, CompactionCallback type alias, CompactionConfig, CompressionMetadata, oldest-turns summarizer), execution loop auto-selection (loop_selector.py, AutoLoopConfig, AutoLoopRule, select_loop_type, build_execution_loop -- complexity-based loop routing with budget-aware downgrade, hybrid fallback, and configurable default_loop_type)
engine/ # Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recovery), context budget management (context_budget.py, ContextBudgetIndicator, fill estimation, token estimation protocol in token_estimation.py), conversation compaction (compaction/, CompactionCallback type alias, CompactionConfig, CompressionMetadata, oldest-turns summarizer), execution loop auto-selection (loop_selector.py, AutoLoopConfig, AutoLoopRule, select_loop_type, build_execution_loop -- complexity-based loop routing with budget-aware downgrade, optional hybrid fallback, and configurable default_loop_type), hybrid execution loop (hybrid_loop.py, HybridLoop -- plan + mini-ReAct steps with per-step turn limits, progress-summary checkpoints, LLM-decided replanning; hybrid_models.py, HybridLoopConfig), shared plan helpers (plan_helpers.py, update_step_status, extract_task_summary, assess_step_success)
hr/ # HR engine: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration sampling, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
memory/ # Persistent agent memory (pluggable MemoryBackend protocol), backends/ (Mem0 adapter: backends/mem0/), retrieval pipeline (ranking, RRF fusion, injection, context formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (consolidation/, dual-mode density-aware archival: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)
persistence/ # Operational data persistence — pluggable PersistenceBackend protocol, SQLite initial, SettingsRepository (namespaced settings CRUD) (see Memory & Persistence design page)
Expand Down
27 changes: 18 additions & 9 deletions docs/design/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,13 @@ All loop implementations satisfy the `ExecutionLoop` runtime-checkable protocol:
```yaml
execution_loop: "hybrid"
hybrid:
planner_model: null
executor_model: null
max_plan_steps: 7
max_turns_per_step: 5
max_replans: 3
checkpoint_after_each_step: true
allow_replan: true
allow_replan_on_completion: true
```

| | |
Expand All @@ -428,8 +431,9 @@ All loop implementations satisfy the `ExecutionLoop` runtime-checkable protocol:
2. **Budget-aware downgrade** -- when monthly budget utilization is at
or above `budget_tight_threshold` (default 80%), hybrid selections
are downgraded to plan_execute to conserve budget.
3. **Hybrid fallback** -- when the hybrid loop is not yet implemented,
falls back to `hybrid_fallback` (default: plan_execute).
3. **Hybrid fallback** -- when `hybrid_fallback` is set (default:
`None`), redirects hybrid selections to the specified loop type.
With `None` (default), the hybrid loop runs directly.

### AgentEngine Orchestrator

Expand Down Expand Up @@ -480,9 +484,9 @@ async run(
`select_loop_type()` with the task's `estimated_complexity` and current
budget utilization (via `BudgetEnforcer.get_budget_utilization_pct()`).
Budget-aware downgrade: hybrid is downgraded to plan_execute when
utilization >= threshold. Hybrid fallback applies when the hybrid loop
is not yet implemented. When no auto config is set, uses the statically
configured loop.
utilization >= threshold. Optional hybrid fallback applies when
`hybrid_fallback` is configured. When no auto config is set, uses
the statically configured loop.
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 Expand Up @@ -599,6 +603,9 @@ sorted per-turn for order-independent comparison.
- **PlanExecuteLoop**: stagnation checked per step (different steps
legitimately repeat similar patterns like read→edit→test); corrections
counter is step-scoped, window resets across step boundaries
- **HybridLoop**: same per-step semantics as PlanExecuteLoop; stagnation
checked within the mini-ReAct sub-loop, corrections counter and
window are step-scoped
- `STAGNATION` termination leaves the task in its current state (like
`MAX_TURNS` — the task is not failed, it's returned to the caller)

Expand Down Expand Up @@ -640,8 +647,8 @@ is derived from `CompressionMetadata.compactions_performed`.
### Compaction Hook

`CompactionCallback` is a type alias (`Callable[[AgentContext], Coroutine[...,
AgentContext | None]]`) wired into both `ReactLoop` and `PlanExecuteLoop` via
their constructors — the same injection pattern as `checkpoint_callback`,
AgentContext | None]]`) wired into `ReactLoop`, `PlanExecuteLoop`, and
`HybridLoop` via their constructors — the same injection pattern as `checkpoint_callback`,
`stagnation_detector`, and `approval_gate`.

The default implementation (`make_compaction_callback` in
Expand Down Expand Up @@ -678,8 +685,10 @@ previously compacted (archived 12 turns). Previous error: ...
boundaries (between completed turns)
- **PlanExecuteLoop**: compaction checked within step execution at turn
boundaries, before stagnation detection
- **HybridLoop**: compaction checked at turn boundaries within the
mini-ReAct sub-loop, same as PlanExecuteLoop

Both loops use the shared `invoke_compaction()` helper from `loop_helpers.py`.
All loops use the shared `invoke_compaction()` helper from `loop_helpers.py`.

---

Expand Down
4 changes: 4 additions & 0 deletions src/synthorg/engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
WorkspaceMergeError,
WorkspaceSetupError,
)
from synthorg.engine.hybrid_loop import HybridLoop
from synthorg.engine.hybrid_models import HybridLoopConfig
from synthorg.engine.loop_protocol import (
BudgetChecker,
ExecutionLoop,
Expand Down Expand Up @@ -282,6 +284,8 @@
"FailAndReassignStrategy",
"Heartbeat",
"HierarchicalAssignmentStrategy",
"HybridLoop",
"HybridLoopConfig",
"InMemoryResourceLock",
"LlmDecompositionConfig",
"LlmDecompositionStrategy",
Expand Down
24 changes: 23 additions & 1 deletion src/synthorg/engine/agent_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import asyncio
import contextlib
import re
import time
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -100,6 +101,7 @@
CoordinationResult,
)
from synthorg.engine.coordination.service import MultiAgentCoordinator
from synthorg.engine.hybrid_models import HybridLoopConfig
from synthorg.engine.loop_protocol import (
BudgetChecker,
ExecutionLoop,
Expand Down Expand Up @@ -159,6 +161,9 @@ class AgentEngine:
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"``.
"""

def __init__( # noqa: PLR0913
Expand All @@ -182,6 +187,7 @@ def __init__( # noqa: PLR0913
coordinator: MultiAgentCoordinator | None = None,
stagnation_detector: StagnationDetector | None = None,
auto_loop_config: AutoLoopConfig | None = None,
hybrid_loop_config: HybridLoopConfig | 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 @@ -195,6 +201,7 @@ def __init__( # noqa: PLR0913
self._parked_context_repo = parked_context_repo
self._stagnation_detector = stagnation_detector
self._auto_loop_config = auto_loop_config
self._hybrid_loop_config = hybrid_loop_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
Expand Down Expand Up @@ -1063,6 +1070,7 @@ async def _resolve_loop(
loop_type,
approval_gate=self._approval_gate,
stagnation_detector=self._stagnation_detector,
hybrid_loop_config=self._hybrid_loop_config,
)

def _make_security_interceptor(
Expand Down Expand Up @@ -1214,7 +1222,21 @@ async def _handle_fatal_error( # noqa: PLR0913
If constructing the error result itself fails, the original
exception is re-raised so it is never silently lost.
"""
error_msg = f"{type(exc).__name__}: {exc}"
raw_msg = str(exc)
# Sanitize: redact paths/URLs, strip non-printable chars,
# and limit length to prevent internal details leaking.
sanitized = re.sub(
r"[A-Za-z]:\\[^\s,;)\"']+"
r"|/(?:home|usr|var|tmp|etc|opt|root|srv|app|data)[^\s,;)\"']+"
r"|\.\.?/[^\s,;)\"']+",
"[REDACTED_PATH]",
raw_msg,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
sanitized = re.sub(r"https?://[^\s,;)\"']+", "[REDACTED_URL]", sanitized)
sanitized = "".join(c for c in sanitized[:200] if c.isprintable())
if not any(c.isalnum() for c in sanitized):
sanitized = "details redacted"
error_msg = f"{type(exc).__name__}: {sanitized}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
logger.exception(
EXECUTION_ENGINE_ERROR,
agent_id=agent_id,
Expand Down
Loading
Loading