> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ComposioHQ/agent-orchestrator/llms.txt
> Use this file to discover all available pages before exploring further.

# Lifecycle Manager

> State machine and reaction engine for orchestrating agent session lifecycles

## Overview

The Lifecycle Manager is the core orchestration engine that polls sessions, detects state transitions, emits events, and triggers automated reactions. It implements a state machine that tracks sessions through their entire lifecycle from spawning to completion.

**Key responsibilities:**

* Periodically poll all active sessions
* Detect state transitions (spawning → working → pr\_open → etc.)
* Emit events on transitions
* Execute automated reactions (auto-fix CI failures, review comments, etc.)
* Escalate to human notification when auto-handling fails

<Note>
  The Lifecycle Manager runs as a background polling loop, typically checking sessions every 30 seconds. It's the primary automation layer between agents and humans.
</Note>

## Architecture

```typescript theme={null}
import { createLifecycleManager } from '@composio/ao-core';

const lifecycleManager = createLifecycleManager({
  config: orchestratorConfig,
  registry: pluginRegistry,
  sessionManager: sessionManager,
});

// Start polling loop (default: 30s interval)
lifecycleManager.start();

// Force check a specific session
await lifecycleManager.check('my-app-1');

// Stop polling
lifecycleManager.stop();
```

## Methods

### start

Start the lifecycle polling loop.

```typescript theme={null}
start(intervalMs?: number): void
```

<ParamField path="intervalMs" type="number" default="30000">
  Polling interval in milliseconds. Default is 30 seconds.
</ParamField>

**Example:**

```typescript theme={null}
// Start with default 30s interval
lifecycleManager.start();

// Start with custom 60s interval
lifecycleManager.start(60_000);
```

<Note>
  The polling loop includes a re-entrancy guard - if a previous poll is still running, subsequent polls are skipped until it completes.
</Note>

***

### stop

Stop the lifecycle polling loop.

```typescript theme={null}
stop(): void
```

**Example:**

```typescript theme={null}
lifecycleManager.stop();
```

<Warning>
  Always call `stop()` before shutting down the orchestrator to clean up the polling interval.
</Warning>

***

### check

Force an immediate state check for a specific session, bypassing the polling schedule.

```typescript theme={null}
check(sessionId: SessionId): Promise<void>
```

<ParamField path="sessionId" type="string" required>
  The session ID to check (e.g., "my-app-1").
</ParamField>

**Example:**

```typescript theme={null}
// Manually trigger a state check
await lifecycleManager.check('my-app-1');
```

**Use cases:**

* After manually updating session metadata
* When you need immediate reaction to state changes
* For testing and debugging

***

### getStates

Get a snapshot of all tracked session states.

```typescript theme={null}
getStates(): Map<SessionId, SessionStatus>
```

<ResponseField name="return" type="Map<SessionId, SessionStatus>">
  Map of session IDs to their current status.
</ResponseField>

**Example:**

```typescript theme={null}
const states = lifecycleManager.getStates();
for (const [sessionId, status] of states) {
  console.log(`${sessionId}: ${status}`);
}
```

## State Machine

The lifecycle manager implements a state machine with the following transitions:

```mermaid theme={null}
graph TD
    A[spawning] --> B[working]
    B --> C[pr_open]
    C --> D{CI Status?}
    D -->|failing| E[ci_failed]
    D -->|passing| F{Review?}
    E --> B
    F -->|pending| G[review_pending]
    F -->|changes_requested| H[changes_requested]
    F -->|approved| I[approved]
    H --> B
    I --> J{Merge ready?}
    J -->|yes| K[mergeable]
    J -->|no| I
    K --> L[merged]
    B --> M[needs_input]
    B --> N[stuck]
    B --> O[errored]
    B --> P[killed]
```

### Session Statuses

<Accordion title="All Session Statuses">
  | Status | Description | Terminal? |
  | - | - | - |
  | `spawning` | Session is being created | No |
  | `working` | Agent is actively working | No |
  | `pr_open` | PR has been created | No |
  | `ci_failed` | CI checks are failing | No |
  | `review_pending` | Waiting for code review | No |
  | `changes_requested` | Reviewer requested changes | No |
  | `approved` | PR approved but not yet mergeable | No |
  | `mergeable` | PR is ready to merge | No |
  | `merged` | PR has been merged | Yes |
  | `needs_input` | Agent is waiting for user input | No |
  | `stuck` | Agent appears to be stuck | No |
  | `errored` | Session encountered an error | Yes |
  | `killed` | Session was terminated | Yes |
  | `done` | Session completed successfully | Yes |
  | `terminated` | Session was forcibly terminated | Yes |
</Accordion>

## Status Detection

The lifecycle manager determines session status by polling multiple sources:

### 1. Runtime Liveness

```typescript theme={null}
const runtime = registry.get<Runtime>('runtime', project.runtime);
const alive = await runtime.isAlive(session.runtimeHandle);
if (!alive) return 'killed';
```

### 2. Agent Activity

Prefers JSONL-based detection (reads agent's session files directly):

```typescript theme={null}
const activityState = await agent.getActivityState(session, config.readyThresholdMs);
if (activityState.state === 'waiting_input') return 'needs_input';
if (activityState.state === 'exited') return 'killed';
```

Falls back to terminal output parsing if JSONL is unavailable.

### 3. PR Detection

```typescript theme={null}
const detectedPR = await scm.detectPR(session, project);
if (detectedPR) {
  session.pr = detectedPR;
  // Persist to metadata
}
```

### 4. CI Status

```typescript theme={null}
const ciStatus = await scm.getCISummary(session.pr);
if (ciStatus === 'failing') return 'ci_failed';
```

### 5. Review State

```typescript theme={null}
const reviewDecision = await scm.getReviewDecision(session.pr);
if (reviewDecision === 'changes_requested') return 'changes_requested';
if (reviewDecision === 'approved') {
  const mergeReady = await scm.getMergeability(session.pr);
  return mergeReady.mergeable ? 'mergeable' : 'approved';
}
```

## Reaction System

Reactions are automated responses to state transitions. They can send messages to agents, notify humans, or trigger actions like auto-merge.

### Reaction Configuration

```yaml theme={null}
# agent-orchestrator.yaml
reactions:
  ci-failed:
    auto: true
    action: send-to-agent
    message: "CI is failing. Run `gh pr checks`, fix issues, and push."
    retries: 2
    escalateAfter: 2
  
  approved-and-green:
    auto: false
    action: notify
    priority: action
    message: "PR is ready to merge"
```

### Reaction Actions

<Accordion title="Available Actions">
  #### send-to-agent

  Sends a message to the agent runtime:

  ```typescript theme={null}
  await sessionManager.send(sessionId, reactionConfig.message);
  ```

  * Retries on failure (configurable via `retries`)
  * Escalates to human after max retries or timeout
  * Non-blocking (session continues running)

  #### notify

  Sends a push notification to configured notifiers:

  ```typescript theme={null}
  const event = createEvent('reaction.triggered', {
    sessionId,
    projectId,
    message: `Reaction '${reactionKey}' triggered notification`,
  });
  await notifyHuman(event, reactionConfig.priority ?? 'info');
  ```

  #### auto-merge

  Triggers automatic PR merge:

  ```typescript theme={null}
  // Triggers SCM plugin to merge the PR
  // Currently posts notification; actual merge logic in SCM plugin
  ```
</Accordion>

### Escalation

Reactions can escalate to human notification after:

<ParamField path="retries" type="number">
  Maximum number of retry attempts before escalating.
</ParamField>

<ParamField path="escalateAfter" type="number | string">
  Escalate after N attempts (number) or duration (string like "30m").
</ParamField>

**Example:**

```yaml theme={null}
reactions:
  ci-failed:
    retries: 2              # Try twice
    escalateAfter: 2        # Then escalate
  
  changes-requested:
    escalateAfter: "30m"    # Escalate after 30 minutes
```

### Reaction Tracking

The lifecycle manager tracks attempts per session:

```typescript theme={null}
interface ReactionTracker {
  attempts: number;
  firstTriggered: Date;
}

// Tracked as "sessionId:reactionKey"
const trackerKey = `${sessionId}:ci-failed`;
```

* Reset when session transitions to a new state
* Persists across polling cycles
* Cleaned up when session is killed/merged

## Events

The lifecycle manager emits events on state transitions. Events are routed to notifiers based on priority.

### Event Types

<Accordion title="Session Lifecycle Events">
  | Event Type | Status Trigger | Priority |
  | - | - | - |
  | `session.spawned` | `spawning` | info |
  | `session.working` | `working` | info |
  | `session.exited` | `killed` | urgent |
  | `session.stuck` | `stuck` | urgent |
  | `session.needs_input` | `needs_input` | urgent |
  | `session.errored` | `errored` | urgent |
</Accordion>

<Accordion title="PR Lifecycle Events">
  | Event Type | Status Trigger | Priority |
  | - | - | - |
  | `pr.created` | `pr_open` | info |
  | `pr.merged` | `merged` | action |
  | `pr.closed` | N/A | warning |
</Accordion>

<Accordion title="CI Events">
  | Event Type | Status Trigger | Priority |
  | - | - | - |
  | `ci.failing` | `ci_failed` | warning |
  | `ci.passing` | N/A | info |
</Accordion>

<Accordion title="Review Events">
  | Event Type | Status Trigger | Priority |
  | - | - | - |
  | `review.pending` | `review_pending` | info |
  | `review.approved` | `approved` | action |
  | `review.changes_requested` | `changes_requested` | warning |
</Accordion>

<Accordion title="Merge Events">
  | Event Type | Status Trigger | Priority |
  | - | - | - |
  | `merge.ready` | `mergeable` | action |
  | `merge.completed` | `merged` | action |
</Accordion>

### Event Structure

```typescript theme={null}
interface OrchestratorEvent {
  id: string;              // UUID
  type: EventType;         // e.g., "ci.failing"
  priority: EventPriority; // urgent | action | warning | info
  sessionId: SessionId;
  projectId: string;
  timestamp: Date;
  message: string;         // Human-readable description
  data: Record<string, unknown>; // Event-specific data
}
```

**Example:**

```typescript theme={null}
{
  id: "a3b4c5d6-e7f8-1234-5678-9abcdef01234",
  type: "ci.failing",
  priority: "warning",
  sessionId: "my-app-1",
  projectId: "my-app",
  timestamp: new Date("2026-03-04T10:30:00Z"),
  message: "my-app-1: pr_open → ci_failed",
  data: { oldStatus: "pr_open", newStatus: "ci_failed" }
}
```

## Notification Routing

Events are routed to notifiers based on priority:

```yaml theme={null}
# agent-orchestrator.yaml
notificationRouting:
  urgent: ["desktop", "composio"]
  action: ["desktop", "composio"]
  warning: ["composio"]
  info: ["composio"]
```

**Priority levels:**

<ParamField path="urgent" type="EventPriority">
  Critical issues requiring immediate attention (stuck, needs\_input, exited).
</ParamField>

<ParamField path="action" type="EventPriority">
  Actionable events (approved, ready to merge, merged).
</ParamField>

<ParamField path="warning" type="EventPriority">
  Issues that may need attention (CI failed, changes requested).
</ParamField>

<ParamField path="info" type="EventPriority">
  Informational updates (session spawned, working).
</ParamField>

## Error Handling

### Graceful Degradation

The lifecycle manager continues operating even when individual checks fail:

```typescript theme={null}
try {
  const activityState = await agent.getActivityState(session);
} catch {
  // Preserve current stuck/needs_input state rather than coercing to "working"
  if (session.status === 'stuck' || session.status === 'needs_input') {
    return session.status;
  }
}
```

### Re-entrancy Guard

Prevents overlapping poll cycles:

```typescript theme={null}
let polling = false;

async function pollAll() {
  if (polling) return; // Skip if previous poll still running
  polling = true;
  try {
    // ... poll all sessions
  } finally {
    polling = false;
  }
}
```

### State Cleanup

Prunes stale entries when sessions are deleted:

```typescript theme={null}
// Remove from state map
for (const trackedId of states.keys()) {
  if (!currentSessionIds.has(trackedId)) {
    states.delete(trackedId);
  }
}

// Remove from reaction trackers
for (const trackerKey of reactionTrackers.keys()) {
  const sessionId = trackerKey.split(':')[0];
  if (!currentSessionIds.has(sessionId)) {
    reactionTrackers.delete(trackerKey);
  }
}
```

## Complete Example

```typescript theme={null}
import { createLifecycleManager } from '@composio/ao-core';

// Create dependencies
const config = loadConfig();
const registry = createPluginRegistry();
await registry.loadBuiltins(config);
const sessionManager = createSessionManager({ config, registry });

// Create lifecycle manager
const lifecycleManager = createLifecycleManager({
  config,
  registry,
  sessionManager,
});

// Start with 15s interval for more responsive automation
lifecycleManager.start(15_000);

// Manually check a session after updating metadata
await sessionManager.send('my-app-1', 'Please fix the type errors');
await lifecycleManager.check('my-app-1');

// Get current state
const states = lifecycleManager.getStates();
console.log('Active sessions:', Array.from(states.entries()));

// Cleanup on shutdown
process.on('SIGINT', () => {
  lifecycleManager.stop();
  process.exit(0);
});
```

## See Also

* [Session Manager](/api/session-manager) - Create and manage sessions
* [Config Loader](/api/config-loader) - Configure reactions and notification routing
* [Plugin: Runtime](/plugins/runtime) - Runtime liveness checks
* [Plugin: Agent](/plugins/agent/claude-code) - Activity detection
* [Plugin: SCM](/plugins/scm) - PR and CI status


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.