Add scoped Agent-mode pipeline as experimental Classic fork.
Wire specialist waves, Brain consolidation, and Classic-compatible reports so Agent mode can run end-to-end via OpenRouter without changing the default Classic path. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Wave scheduler for the Agent pipeline."""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable, Dict, Iterable, List, Optional
|
||||
|
||||
from backend.agents.base import AgentResult, AgentScope, ScopedAgent
|
||||
from backend.agents.memory import ProjectMemory
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentStats:
|
||||
"""Job-local accounting; never shared across concurrent jobs."""
|
||||
|
||||
calls: int = 0
|
||||
scopes: int = 0
|
||||
merges: int = 0
|
||||
failed_scopes: List[str] = field(default_factory=list)
|
||||
|
||||
def as_dict(self) -> Dict:
|
||||
return {
|
||||
"calls": self.calls,
|
||||
"scopes": self.scopes,
|
||||
"merges": self.merges,
|
||||
"failed_scopes": list(self.failed_scopes),
|
||||
}
|
||||
|
||||
|
||||
class Orchestrator:
|
||||
"""Coordinates bounded fan-out/fan-in waves against one ProjectMemory."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
memory: ProjectMemory,
|
||||
on_stage: Optional[Callable[[str], None]] = None,
|
||||
) -> None:
|
||||
self.memory = memory
|
||||
self.on_stage = on_stage
|
||||
self.stats = AgentStats()
|
||||
|
||||
def stage(self, name: str) -> None:
|
||||
print(f"\n=== {name} ===")
|
||||
if self.on_stage:
|
||||
self.on_stage(name)
|
||||
|
||||
def initialize(self) -> Dict:
|
||||
"""Initialize the job-local artifact store."""
|
||||
self.stage("Initialize agent pipeline")
|
||||
self.memory.dump()
|
||||
return self.stats.as_dict()
|
||||
|
||||
def run_scopes(
|
||||
self,
|
||||
agent: ScopedAgent,
|
||||
scopes: Iterable[AgentScope],
|
||||
concurrency: int,
|
||||
) -> List[AgentResult]:
|
||||
"""Run independent scopes; one failure never aborts the wave."""
|
||||
scope_list = list(scopes)
|
||||
if not scope_list:
|
||||
return []
|
||||
results: List[AgentResult] = []
|
||||
with ThreadPoolExecutor(max_workers=max(1, concurrency)) as pool:
|
||||
futures = {pool.submit(agent.run, scope): scope for scope in scope_list}
|
||||
for future in as_completed(futures):
|
||||
scope = futures[future]
|
||||
self.stats.scopes += 1
|
||||
try:
|
||||
result = future.result()
|
||||
except Exception as exc:
|
||||
result = AgentResult(scope_id=scope.scope_id, error=str(exc))
|
||||
if result.error:
|
||||
self.stats.failed_scopes.append(
|
||||
f"{agent.name}:{scope.scope_id}: {result.error}"
|
||||
)
|
||||
results.append(result)
|
||||
results.sort(key=lambda result: result.scope_id)
|
||||
return results
|
||||
Reference in New Issue
Block a user