> ## 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.

# Session Manager

> CRUD operations for agent sessions - spawn, restore, list, kill, and send messages

## Overview

The Session Manager orchestrates the Runtime, Agent, and Workspace plugins to create, manage, and destroy agent sessions. It handles the full session lifecycle from creation to cleanup.

**Core responsibilities:**

* Spawn new sessions (create workspace → runtime → launch agent)
* Restore crashed/killed sessions
* List all sessions with live state enrichment
* Kill sessions and clean up resources
* Send messages to running sessions
* Automatic cleanup of completed work

<Note>
  The Session Manager is stateless - all session data is stored in flat metadata files. It rebuilds session state on every operation by reading these files and polling plugins.
</Note>

## Architecture

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

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

// Spawn a new session
const session = await sessionManager.spawn({
  projectId: 'my-app',
  issueId: 'INT-1234',
  prompt: 'Fix the login bug',
});

// List all sessions
const sessions = await sessionManager.list();

// Send a message
await sessionManager.send('my-app-1', 'Please add unit tests');

// Kill a session
await sessionManager.kill('my-app-1');
```

## Methods

### spawn

Create a new agent session.

```typescript theme={null}
spawn(config: SessionSpawnConfig): Promise<Session>
```

<ParamField path="config.projectId" type="string" required>
  Project ID from `agent-orchestrator.yaml` (e.g., "my-app").
</ParamField>

<ParamField path="config.issueId" type="string">
  Issue identifier (e.g., "INT-1234", "#42"). Can be tracker issue or free-text.
</ParamField>

<ParamField path="config.branch" type="string">
  Git branch name. If not specified, inferred from issue or session ID.
</ParamField>

<ParamField path="config.prompt" type="string">
  Initial prompt for the agent. Combined with issue context if available.
</ParamField>

<ParamField path="config.agent" type="string">
  Override agent plugin for this session (e.g., "codex", "claude-code").
</ParamField>

**Returns:**

<ResponseField name="Session" type="object">
  The newly created session object with all metadata.
</ResponseField>

**Example:**

```typescript theme={null}
// Spawn with issue ID (fetches issue from tracker)
const session = await sessionManager.spawn({
  projectId: 'my-app',
  issueId: 'INT-1234',
  prompt: 'Add TypeScript types',
});

// Spawn with explicit branch
const session2 = await sessionManager.spawn({
  projectId: 'my-app',
  branch: 'feature/new-dashboard',
  prompt: 'Build the dashboard UI',
});

// Spawn with agent override
const session3 = await sessionManager.spawn({
  projectId: 'my-app',
  agent: 'codex',  // Use Codex instead of default
  prompt: 'Refactor the API',
});
```

### Spawn Process

The spawn process follows these steps:

1. **Validate project** - Check that projectId exists in config
2. **Validate issue** - If issueId provided, fetch from tracker (fails fast on auth/network errors)
3. **Reserve session ID** - Atomically create unique ID (e.g., "my-app-1")
4. **Create workspace** - Use Workspace plugin to create isolated environment
5. **Generate prompt** - Combine user prompt + issue context + project rules
6. **Create runtime** - Launch agent process in runtime environment
7. **Write metadata** - Persist session state to disk
8. **Post-launch setup** - Configure agent (optional plugin hook)
9. **Send initial prompt** - For agents with `promptDelivery: "post-launch"`

<Warning>
  If any step fails after workspace creation, the Session Manager automatically cleans up all created resources (workspace, runtime, metadata) to prevent resource leaks.
</Warning>

### Branch Name Resolution

Branch names are determined by priority:

```typescript theme={null}
if (config.branch) {
  // 1. Explicit branch always wins
  branch = config.branch;
} else if (config.issueId && tracker) {
  // 2. Let tracker generate branch name
  branch = tracker.branchName(config.issueId, project);
} else if (config.issueId) {
  // 3. Generate from issue ID
  branch = `feat/${sanitize(config.issueId)}`;
} else {
  // 4. Fallback to session ID
  branch = `session/${sessionId}`;
}
```

***

### spawnOrchestrator

Create an orchestrator session that manages other sessions.

```typescript theme={null}
spawnOrchestrator(config: OrchestratorSpawnConfig): Promise<Session>
```

<ParamField path="config.projectId" type="string" required>
  Project ID from config.
</ParamField>

<ParamField path="config.systemPrompt" type="string">
  System prompt with orchestrator instructions. Automatically written to file to avoid shell truncation.
</ParamField>

**Returns:**

<ResponseField name="Session" type="object">
  The orchestrator session with `role: "orchestrator"` metadata.
</ResponseField>

**Example:**

```typescript theme={null}
import { readFileSync } from 'node:fs';

const systemPrompt = readFileSync('./orchestrator-prompt.md', 'utf-8');

const orchestrator = await sessionManager.spawnOrchestrator({
  projectId: 'my-app',
  systemPrompt,
});

// Session ID: "my-app-orchestrator"
// Permissions: always "skip" (no permission prompts)
```

<Note>
  Orchestrator sessions run in the project directory (not a worktree) and have permissions set to "skip" so they can autonomously run `ao` CLI commands.
</Note>

***

### list

List all sessions, optionally filtered by project.

```typescript theme={null}
list(projectId?: string): Promise<Session[]>
```

<ParamField path="projectId" type="string">
  Filter to sessions from this project only. If omitted, returns all sessions.
</ParamField>

**Returns:**

<ResponseField name="Session[]" type="array">
  Array of session objects enriched with live runtime state and agent activity.
</ResponseField>

**Example:**

```typescript theme={null}
// List all sessions
const allSessions = await sessionManager.list();

// List sessions for specific project
const appSessions = await sessionManager.list('my-app');

// Display session info
for (const session of allSessions) {
  console.log(`${session.id}: ${session.status} (${session.activity ?? 'unknown'})`);
  if (session.pr) console.log(`  PR: ${session.pr.url}`);
}
```

### Session Enrichment

The `list()` method enriches sessions with live state:

```typescript theme={null}
// 1. Read metadata from disk
const raw = readMetadataRaw(sessionsDir, sessionId);
const session = metadataToSession(sessionId, raw);

// 2. Check runtime liveness (with 2s timeout per session)
if (session.runtimeHandle) {
  const alive = await runtime.isAlive(session.runtimeHandle);
  if (!alive) session.status = 'killed';
}

// 3. Detect agent activity (reads JSONL files)
const activity = await agent.getActivityState(session);
if (activity) {
  session.activity = activity.state;
  session.lastActivityAt = activity.timestamp ?? session.lastActivityAt;
}

// 4. Enrich with agent session info (summary, cost)
const info = await agent.getSessionInfo(session);
if (info) session.agentInfo = info;
```

<Warning>
  Enrichment includes subprocess calls (tmux/ps checks) which can be slow under load. Each session has a 2-second timeout. If enrichment times out, the session keeps its metadata values.
</Warning>

***

### get

Get a single session by ID.

```typescript theme={null}
get(sessionId: SessionId): Promise<Session | null>
```

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

**Returns:**

<ResponseField name="Session | null" type="object">
  Session object if found, null otherwise.
</ResponseField>

**Example:**

```typescript theme={null}
const session = await sessionManager.get('my-app-1');

if (!session) {
  console.error('Session not found');
  return;
}

console.log(`Status: ${session.status}`);
console.log(`Branch: ${session.branch}`);
console.log(`Workspace: ${session.workspacePath}`);
```

***

### restore

Restore a crashed or killed session.

```typescript theme={null}
restore(sessionId: SessionId): Promise<Session>
```

<ParamField path="sessionId" type="string" required>
  Session ID to restore.
</ParamField>

**Returns:**

<ResponseField name="Session" type="object">
  The restored session with updated `restoredAt` timestamp.
</ResponseField>

**Throws:**

* `SessionNotRestorableError` - Session is not in a terminal state or is merged
* `WorkspaceMissingError` - Workspace doesn't exist and can't be recreated

**Example:**

```typescript theme={null}
try {
  const session = await sessionManager.restore('my-app-1');
  console.log('Session restored:', session.id);
} catch (err) {
  if (err instanceof SessionNotRestorableError) {
    console.error('Cannot restore:', err.reason);
  } else if (err instanceof WorkspaceMissingError) {
    console.error('Workspace missing:', err.path);
  }
}
```

### Restore Process

1. **Find metadata** - Check active sessions, fall back to archive
2. **Validate restorability** - Must be terminal state (killed/errored) but not merged
3. **Check workspace** - Verify workspace exists or attempt recreation
4. **Destroy old runtime** - Kill any orphaned runtime processes
5. **Get launch command** - Try agent's restore command, fall back to fresh launch
6. **Create runtime** - Launch agent with restored environment
7. **Update metadata** - Set status to "spawning", record `restoredAt`

<Accordion title="Restorable vs Non-Restorable Sessions">
  **Restorable:**

  * `killed` - Agent process exited
  * `errored` - Session encountered an error
  * `done` - Session completed
  * `terminated` - Forcibly terminated
  * `cleanup` - Cleaned up but can be restored

  **Non-restorable:**

  * `merged` - PR already merged (cannot reopen)
  * `working`, `pr_open`, etc. - Session is still active
</Accordion>

***

### kill

Terminate a session and clean up all resources.

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

<ParamField path="sessionId" type="string" required>
  Session ID to kill.
</ParamField>

**Example:**

```typescript theme={null}
await sessionManager.kill('my-app-1');
console.log('Session killed');
```

### Kill Process

1. **Find session** - Locate metadata across all projects
2. **Destroy runtime** - Stop agent process (tmux kill, docker stop, etc.)
3. **Destroy workspace** - Delete worktree or clone (unless it's the project path)
4. **Archive metadata** - Move to `archive/` subdirectory with timestamp

<Note>
  Workspaces that match the project path are NOT deleted (orchestrator sessions run in the main repo).
</Note>

***

### cleanup

Automatically clean up completed sessions.

```typescript theme={null}
cleanup(projectId?: string, options?: { dryRun?: boolean }): Promise<CleanupResult>
```

<ParamField path="projectId" type="string">
  Limit cleanup to this project.
</ParamField>

<ParamField path="options.dryRun" type="boolean" default="false">
  Preview what would be cleaned up without actually killing sessions.
</ParamField>

**Returns:**

<ResponseField name="CleanupResult" type="object">
  ```typescript theme={null}
  interface CleanupResult {
    killed: string[];    // Sessions that were (or would be) killed
    skipped: string[];   // Sessions that were skipped
    errors: Array<{      // Sessions that failed to clean up
      sessionId: string;
      error: string;
    }>;
  }
  ```
</ResponseField>

**Example:**

```typescript theme={null}
// Dry run to see what would be cleaned
const preview = await sessionManager.cleanup(undefined, { dryRun: true });
console.log('Would kill:', preview.killed);
console.log('Would skip:', preview.skipped);

// Actually clean up
const result = await sessionManager.cleanup();
console.log('Killed:', result.killed);
if (result.errors.length > 0) {
  console.error('Errors:', result.errors);
}
```

### Cleanup Criteria

Sessions are killed if ANY of these conditions are met:

1. **PR is merged or closed** - Work is complete
2. **Issue is completed** - Work is done
3. **Runtime is dead** - Agent process no longer running

**Never cleaned up:**

* Orchestrator sessions (checked by `role: "orchestrator"` or ID ending in "-orchestrator")
* Sessions where checks fail (can't verify state)

***

### send

Send a message to a running session.

```typescript theme={null}
send(sessionId: SessionId, message: string): Promise<void>
```

<ParamField path="sessionId" type="string" required>
  Session ID to send message to.
</ParamField>

<ParamField path="message" type="string" required>
  Message text to send to the agent.
</ParamField>

**Example:**

```typescript theme={null}
await sessionManager.send('my-app-1', 
  'CI is failing. Run `gh pr checks` to see failures and fix them.'
);
```

**How it works:**

```typescript theme={null}
// 1. Find session metadata
const raw = readMetadataRaw(sessionsDir, sessionId);

// 2. Build runtime handle (stored or fabricated)
const handle = raw.runtimeHandle 
  ? JSON.parse(raw.runtimeHandle)
  : { id: sessionId, runtimeName: config.defaults.runtime };

// 3. Send via runtime plugin
const runtime = registry.get<Runtime>('runtime', handle.runtimeName);
await runtime.sendMessage(handle, message);
```

<Note>
  For tmux runtime, this uses `tmux send-keys` to type the message. For other runtimes, it may use stdin, API calls, or other mechanisms.
</Note>

## Session Object

The `Session` interface represents a running agent session:

```typescript theme={null}
interface Session {
  id: SessionId;              // Unique ID (e.g., "my-app-1")
  projectId: string;          // Config key (e.g., "my-app")
  status: SessionStatus;      // Current lifecycle status
  activity: ActivityState | null;  // Agent activity (active, ready, idle, etc.)
  branch: string | null;      // Git branch name
  issueId: string | null;     // Issue identifier
  pr: PRInfo | null;          // PR metadata if PR exists
  workspacePath: string | null;  // Path to workspace on disk
  runtimeHandle: RuntimeHandle | null;  // Runtime-specific handle
  agentInfo: AgentSessionInfo | null;  // Agent summary, cost, etc.
  createdAt: Date;            // When session was created
  lastActivityAt: Date;       // Last detected activity
  restoredAt?: Date;          // When session was restored (if ever)
  metadata: Record<string, string>;  // Raw metadata key-value pairs
}
```

### Activity States

<ParamField path="active" type="ActivityState">
  Agent is processing (thinking, writing code).
</ParamField>

<ParamField path="ready" type="ActivityState">
  Agent finished its turn, alive and waiting for input.
</ParamField>

<ParamField path="idle" type="ActivityState">
  Agent has been inactive for a while (stale).
</ParamField>

<ParamField path="waiting_input" type="ActivityState">
  Agent is asking a question / permission prompt.
</ParamField>

<ParamField path="blocked" type="ActivityState">
  Agent hit an error or is stuck.
</ParamField>

<ParamField path="exited" type="ActivityState">
  Agent process is no longer running.
</ParamField>

## Metadata Files

Sessions are stored as flat key=value files:

**File path:**

```
~/.agent-orchestrator/{hash}-{projectId}/sessions/{sessionId}
```

**Example file (`my-app-1`):**

```ini theme={null}
project=my-app
worktree=/Users/foo/.agent-orchestrator/a3b4c5d6e7f8-my-app/worktrees/my-app-1
branch=feat/INT-1234
status=working
tmuxName=a3b4c5d6e7f8-my-app-1
pr=https://github.com/org/repo/pull/42
issue=INT-1234
summary=Fix login authentication bug
agent=claude-code
createdAt=2026-03-04T10:30:00.000Z
runtimeHandle={"id":"a3b4c5d6e7f8-my-app-1","runtimeName":"tmux"}
```

<Note>
  Metadata files use user-facing session names ("my-app-1"), not tmux session names. The `tmuxName` field maps to the globally unique tmux session.
</Note>

## Error Handling

### Resource Cleanup on Failure

The Session Manager automatically cleans up resources when spawn fails:

```typescript theme={null}
try {
  const handle = await runtime.create(...);
} catch (err) {
  // Clean up workspace
  if (workspace && workspacePath !== project.path) {
    await workspace.destroy(workspacePath);
  }
  // Clean up reserved session ID
  deleteMetadata(sessionsDir, sessionId, false);
  throw err;
}
```

### Issue Validation

Issue validation fails fast to prevent creating resources for invalid issues:

```typescript theme={null}
if (config.issueId && tracker) {
  try {
    await tracker.getIssue(config.issueId, project);
  } catch (err) {
    if (isIssueNotFoundError(err)) {
      // Ad-hoc issue string - proceed without tracker
    } else {
      // Auth/network error - fail fast
      throw new Error(`Failed to fetch issue: ${err}`);
    }
  }
}
```

### Graceful Degradation

Non-critical failures don't stop the session:

```typescript theme={null}
// Post-launch prompt delivery failure is non-fatal
if (agent.promptDelivery === 'post-launch' && prompt) {
  try {
    await runtime.sendMessage(handle, prompt);
  } catch {
    // Agent is running but didn't get the prompt
    // User can retry with `ao send`
  }
}
```

## Complete Example

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

// Setup
const config = loadConfig();
const registry = createPluginRegistry();
await registry.loadBuiltins(config);

const sessionManager = createSessionManager({
  config,
  registry,
});

// Spawn a new session
const session = await sessionManager.spawn({
  projectId: 'my-app',
  issueId: 'INT-1234',
  prompt: 'Add TypeScript types to the API',
});
console.log('Created session:', session.id);

// List all sessions
const sessions = await sessionManager.list();
for (const s of sessions) {
  console.log(`${s.id}: ${s.status} (${s.activity})`);
}

// Send a message
await sessionManager.send(session.id, 
  'Please add unit tests for the new types'
);

// Wait for work to complete...

// Clean up finished sessions
const cleanup = await sessionManager.cleanup();
console.log('Cleaned up:', cleanup.killed);

// Restore if needed
if (cleanup.killed.includes(session.id)) {
  const restored = await sessionManager.restore(session.id);
  console.log('Restored:', restored.id);
}
```

## See Also

* [Lifecycle Manager](/api/lifecycle-manager) - State machine and reactions
* [Config Loader](/api/config-loader) - Project configuration
* [Plugin: Runtime](/plugins/runtime) - Session execution environments
* [Plugin: Agent](/plugins/agent/claude-code) - AI coding tools
* [Plugin: Workspace](/plugins/workspace) - Code isolation strategies


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