feat(lifecycle): consolidate agent lifecycle onto AgentPolicy#6
feat(lifecycle): consolidate agent lifecycle onto AgentPolicy#6lloyal-research merged 6 commits intomainfrom
Conversation
BREAKING CHANGES: - extractionPrompt removed from AgentPoolOptions/SpawnAgentsOpts - pressure removed from AgentPoolOptions/SpawnAgentsOpts - ProduceAction.nudge.message now required (was optional) - SettleAction.nudge.message now required - Agent.nudged and Agent.markNudged() removed Changes: - Consolidated lifecycle decisions onto DefaultAgentPolicy: - shouldExit(agent, pressure) — pre-produce kill gate - onRecovery(agent) — scratchpad extraction with confabulation guard - budget config — unified KV and wall-time thresholds - pressureThresholds getter — pool reads once at setup - Stateless nudge — fires every turn over budget - underPressure/overBudget split — early reporting regardless of minToolCalls - Explore/exploit (shouldExplore) unchanged — decoupled from lifecycle - ContextPressure: added nCtx, cellsUsed, percentAvailable; consolidated bypass sites - Scoring: added scoreRelevanceBatch to EntailmentScorer - Trace: explore + percentAvailable on tool:dispatch; echo guard always emits - fetch-page: guard against null documentElement from parseHTML
There was a problem hiding this comment.
Pull request overview
This PR consolidates agent lifecycle management onto the AgentPolicy class, moving decision logic from the AgentPool and Agent classes into a policy interface. The changes support dynamic lifecycle controls, time budgets, and scratchpad extraction for killed agents, while decoupling explore/exploit behavior from lifecycle management.
Changes:
- Removed
pressureandextractionPromptparameters fromAgentPoolOptionsandSpawnAgentsOpts, consolidating these intoDefaultAgentPolicyconfiguration - Made
ProduceAction.nudge.messageandSettleAction.nudge.messagerequired (previously optional) - Removed
Agent.nudgedfield andAgent.markNudged()method, replacing nudge escalation with stateless per-turn nudging - Added
AgentPolicyinterface methods:shouldExit()(pre-produce kill gate),pressureThresholdsgetter, andonRecovery()(scratchpad extraction) - Expanded
DefaultAgentPolicywith budget configuration (context KV limits and time limits) and recovery configuration - Updated
ContextPressureto includenCtx,cellsUsed, and computedpercentAvailablefield - Updated trace types with new exit/nudge reasons and added
explore/percentAvailableto tool:dispatch events - Enhanced scratchpad extraction with prompt caching and policy-driven per-agent decisions
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rig/src/tools/fetch-page.ts | Added null check for documentElement to prevent crashes from parseHTML |
| packages/agents/src/AgentPolicy.ts | Consolidated lifecycle logic: added shouldExit, pressureThresholds, onRecovery, budget/recovery config |
| packages/agents/src/Agent.ts | Removed nudged tracking and markNudged() method |
| packages/agents/src/agent-pool.ts | Integrated policy lifecycle methods, moved recovery to policy, added explore/percentAvailable to traces |
| packages/agents/src/spawn-agents.ts | Removed extractionPrompt/pressure params, refactored echo guard to always emit trace |
| packages/agents/src/types.ts | Removed pressure/extractionPrompt from AgentPoolOptions, updated policy documentation |
| packages/agents/src/trace-types.ts | Added time_exceeded/policy_exit drop reasons, time_nudge nudge reason, explore/percentAvailable to tool:dispatch |
| packages/agents/src/index.ts | Exported RecoveryAction type |
| packages/agents/test/AgentPolicy.test.ts | Removed nudged param, added comprehensive tests for shouldExit, onRecovery, budget, stateless nudging |
| packages/agents/test/spawn-agents.test.ts | Added tests for explore/exploit decoupling and EntailmentScorer interface |
| packages/agents/test/source.test.ts | Added tests for ContextPressure fields |
| packages/agents/test/Agent.test.ts | Removed test for markNudged method |
| examples/supervisor/harness.ts | Updated to use DefaultAgentPolicy with budget instead of pressure param |
| examples/reflection/harness.ts | Updated to use DefaultAgentPolicy with budget instead of pressure param |
| examples/react-agent/harness.ts | Updated to use DefaultAgentPolicy with budget instead of pressure param |
| examples/deep-research-web/harness.ts | Consolidated recovery/budget into researchPolicy, removed extractionPrompt params |
| </return_format> |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
examples/supervisor/harness.ts
Outdated
| import { DefaultAgentPolicy, call } from 'effection'; | ||
| import type { Operation, Channel } from 'effection'; | ||
| import { Session } from '@lloyal-labs/sdk'; | ||
| import { DefaultAgentPolicy, Session } from '@lloyal-labs/sdk'; | ||
| import type { SessionContext } from '@lloyal-labs/sdk'; | ||
| import { | ||
| import { DefaultAgentPolicy, | ||
| Ctx, generate, useAgentPool, runAgents, withSharedRoot, | ||
| } from '@lloyal-labs/lloyal-agents'; | ||
| import type { Tool, AgentPoolResult } from '@lloyal-labs/lloyal-agents'; | ||
| import type { WorkflowEvent } from './tui'; | ||
| import { reportTool } from '@lloyal-labs/rig'; | ||
| import { DefaultAgentPolicy, reportTool } from '@lloyal-labs/rig'; |
There was a problem hiding this comment.
DefaultAgentPolicy is imported from incorrect modules. It should only be imported from '@lloyal-labs/lloyal-agents' (line 7), but it also appears in imports from 'effection' (line 3), '@lloyal-labs/sdk' (line 5), and '@lloyal-labs/rig' (line 12). These packages do not export DefaultAgentPolicy, which will cause import errors at runtime.
examples/reflection/harness.ts
Outdated
| import { DefaultAgentPolicy, call, ensure } from 'effection'; | ||
| import type { Operation, Channel } from 'effection'; | ||
| import { Branch, Session, buildUserDelta } from '@lloyal-labs/sdk'; | ||
| import { DefaultAgentPolicy, Branch, Session, buildUserDelta } from '@lloyal-labs/sdk'; | ||
| import type { SessionContext } from '@lloyal-labs/sdk'; | ||
| import { | ||
| import { DefaultAgentPolicy, | ||
| Ctx, useAgentPool, runAgents, diverge, withSharedRoot, | ||
| } from '@lloyal-labs/lloyal-agents'; | ||
| import type { Tool, AgentPoolResult, DivergeResult } from '@lloyal-labs/lloyal-agents'; | ||
| import type { WorkflowEvent } from './tui'; | ||
| import { reportTool } from '@lloyal-labs/rig'; | ||
| import { DefaultAgentPolicy, reportTool } from '@lloyal-labs/rig'; |
There was a problem hiding this comment.
DefaultAgentPolicy is imported from incorrect modules. It should only be imported from '@lloyal-labs/lloyal-agents' (line 7), but it also appears in imports from 'effection' (line 3), '@lloyal-labs/sdk' (line 5), and '@lloyal-labs/rig' (line 12). These packages do not export DefaultAgentPolicy, which will cause import errors at runtime.
| tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(), | ||
| type: 'pool:agentNudge', agentId: a.id, reason: 'pressure_softcut' }); |
There was a problem hiding this comment.
The nudge trace event at line 442 always uses 'pressure_softcut' as the reason, but the policy can emit nudges for different reasons: time limits, turn limits, tool guards, and KV pressure. The trace event should distinguish between these cases. Consider adding a reason field to ProduceAction.nudge or inferring the reason from the nudge message in agent-pool.ts.
| tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(), | |
| type: 'pool:agentNudge', agentId: a.id, reason: 'pressure_softcut' }); | |
| const lowerMsg = (action.message || '').toLowerCase(); | |
| const nudgeReason = | |
| lowerMsg.includes('time') ? 'time_limit' : | |
| lowerMsg.includes('turn') ? 'max_turns' : | |
| (lowerMsg.includes('tool') || lowerMsg.includes('guard')) ? 'tool_guard' : | |
| lowerMsg.includes('pressure') ? 'pressure_softcut' : | |
| 'unknown'; | |
| tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(), | |
| type: 'pool:agentNudge', agentId: a.id, reason: nudgeReason }); |
packages/agents/src/agent-pool.ts
Outdated
| if (policy.shouldExit?.(a, pressure) ?? pressure.critical) { | ||
| a.transition('idle'); | ||
| const exitReason = pressure.critical ? 'pressure_critical' as const | ||
| : policy.shouldExit ? 'policy_exit' as const |
There was a problem hiding this comment.
The exitReason logic at line 386-388 checks if the policy.shouldExit method exists (which is always true if the policy has the method), not whether it returned true. This causes incorrect trace event reasons. The logic should check if policy.shouldExit?.(a, pressure) returned true, or determine the exit reason from within the shouldExit call.
| if (policy.shouldExit?.(a, pressure) ?? pressure.critical) { | |
| a.transition('idle'); | |
| const exitReason = pressure.critical ? 'pressure_critical' as const | |
| : policy.shouldExit ? 'policy_exit' as const | |
| const shouldExitByPolicy = policy.shouldExit?.(a, pressure); | |
| if ((shouldExitByPolicy ?? pressure.critical)) { | |
| a.transition('idle'); | |
| const exitReason = pressure.critical ? 'pressure_critical' as const | |
| : shouldExitByPolicy ? 'policy_exit' as const |
BREAKING CHANGES:
Changes: