Agent Runtime
The Agent Runtime coordinates the lifecycle of goals, agents, and cognitive resources across the WASP platform.
AgentRuntime (src/agent_manager/runtime.py)
AgentRuntime manages individual agent instances within the AgentOrchestrator:
- Each agent has a goal queue (stored in Redis)
- The runtime ticks active agents every 15 seconds (
AgentTickJob) - Sorts goals by priority before each tick
- Enforces CPU backpressure threshold (default: 85%)
Agent Lifecycle
create_agent()
│
▼
Agent(status=IDLE, goal_queue=[])
│
▼ (goal assigned)
Agent(status=RUNNING, current_goal=Goal(...))
│
├── goal completes → Agent(status=IDLE)
├── goal fails → Agent(status=ERROR) → retry
└── agent paused → Agent(status=PAUSED)
CPU Backpressure
Before processing each goal tick, the runtime checks CPU usage:
cpu_pct = await asyncio.to_thread(psutil.cpu_percent, interval=0.1)
if cpu_pct > settings.agents_cpu_threshold: # default: 85%
return # Skip this tick
Using asyncio.to_thread() prevents the blocking psutil.cpu_percent() call from stalling the event loop.
GoalOrchestrator (src/goal_orchestrator/orchestrator.py)
The central coordinator for autonomous goal execution:
Key Methods
| Method | Description |
|---|---|
create_goal(objective, chat_id, priority, source) | Create and persist a new goal |
tick() | Advance all active goals (called every 15s by GoalTickJob) |
_tick_one(goal) | Advance a single goal by up to 3 steps |
invoke(goal_id) | Execute a specific goal on-demand |
get_goal(goal_id) | Retrieve goal state |
list_goals(status) | List goals by status |
delete_goal(goal_id) | Remove goal from Redis |
Concurrent Execution
The orchestrator processes up to GOAL_MAX_CONCURRENT goals (default: 3) per tick using asyncio.gather():
active_goals = sorted(
[g for g in all_goals if g.status == GoalStatus.ACTIVE],
key=lambda g: g.priority,
reverse=True, # Higher priority first
)[:self.max_concurrent]
await asyncio.gather(*[self._tick_one(g) for g in active_goals])
Steps Per Tick
Each tick, a goal can advance up to 3 consecutive steps:
async def _tick_one(self, goal: Goal) -> None:
for step in range(3): # Up to 3 steps per tick
result = await self._execute_next_task(goal)
if result.done or result.waiting:
break
This allows faster goal completion without waiting 15 seconds between each step.
CognitiveBudget
Each goal tracks its resource usage:
@dataclass
class CognitiveBudget:
tokens_planning: int = 0 # Tokens used for planning
tokens_execution: int = 0 # Tokens used in execution
replans: int = 0 # Number of replans
memory_bytes: int = 0 # Working memory used
start_time: float = field(default_factory=time.time)
Budget is checked before each step:
if budget.tokens_execution >= settings.goal_budget_max_tokens_execution:
goal.status = GoalStatus.FAILED
goal.fail_reason = "budget_exceeded"
Service Registry
All major services are registered in a central service registry (src/runtime/registry.py):
registry.register(SERVICE_MEMORY, memory)
registry.register(SERVICE_MODELS, model_manager)
registry.register(SERVICE_SKILLS, skill_registry)
registry.register(SERVICE_EXECUTOR, skill_executor)
registry.register(SERVICE_SCHEDULER, scheduler)
registry.register(SERVICE_BUS, bus)
registry.register(SERVICE_HEALTH, health_monitor)
registry.register(SERVICE_INTROSPECTOR, introspector)
registry.register(SERVICE_BROKER, broker_client)
registry.register(SERVICE_METRICS, metrics_collector)
registry.register(SERVICE_ECONOMICS, economics_tracker)
This allows any component to access services without circular imports.
State Persistence
Goal state is persisted in Redis:
goals → Hash: {goal_id: json_blob}
agents → Hash: {agent_id: json_blob}
All goal state is serialized with Pydantic and stored as JSON. Goals survive container restarts.
Goal Meta-Reflection
GoalMetaReflectionJob (runs every 5 minutes):
- Analyzes goal health across the last hour
- Detects replan storms (>6 replans in 10 minutes)
- Logs meta-state for monitoring
- Sends Telegram alert if storm detected