Skip to content

feat(lifecycle): consolidate agent lifecycle onto AgentPolicy#6

Merged
lloyal-research merged 6 commits intomainfrom
feat/ramp-down
Apr 1, 2026
Merged

feat(lifecycle): consolidate agent lifecycle onto AgentPolicy#6
lloyal-research merged 6 commits intomainfrom
feat/ramp-down

Conversation

@lloyal-research
Copy link
Copy Markdown
Contributor

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

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
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 pressure and extractionPrompt parameters from AgentPoolOptions and SpawnAgentsOpts, consolidating these into DefaultAgentPolicy configuration
  • Made ProduceAction.nudge.message and SettleAction.nudge.message required (previously optional)
  • Removed Agent.nudged field and Agent.markNudged() method, replacing nudge escalation with stateless per-turn nudging
  • Added AgentPolicy interface methods: shouldExit() (pre-produce kill gate), pressureThresholds getter, and onRecovery() (scratchpad extraction)
  • Expanded DefaultAgentPolicy with budget configuration (context KV limits and time limits) and recovery configuration
  • Updated ContextPressure to include nCtx, cellsUsed, and computed percentAvailable field
  • Updated trace types with new exit/nudge reasons and added explore/percentAvailable to 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.

Comment on lines +3 to +12
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';
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +12
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';
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +442
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
type: 'pool:agentNudge', agentId: a.id, reason: 'pressure_softcut' });
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 });

Copilot uses AI. Check for mistakes.
Comment on lines +384 to +387
if (policy.shouldExit?.(a, pressure) ?? pressure.critical) {
a.transition('idle');
const exitReason = pressure.critical ? 'pressure_critical' as const
: policy.shouldExit ? 'policy_exit' as const
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
@lloyal-research lloyal-research merged commit 76777bd into main Apr 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants