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

# Config Loader

> Load and validate agent-orchestrator.yaml configuration with Zod schemas

## Overview

The Config Loader reads `agent-orchestrator.yaml`, validates it with Zod schemas, applies defaults, and expands paths. It provides a type-safe configuration object for the entire orchestrator.

**Key features:**

* Auto-discovery of config files (searches up directory tree like git)
* Zod validation with helpful error messages
* Automatic defaults for all optional fields
* Path expansion (\~ to home directory)
* Project uniqueness validation
* Default reaction configuration

<Note>
  The config loader follows a search order similar to git: check `AO_CONFIG_PATH` env var, search up from CWD, check home directory locations.
</Note>

## Usage

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

// Auto-discover config file
const config = loadConfig();

// Explicit path
const config2 = loadConfig('./agent-orchestrator.yaml');

// Get config with resolved path
const { config: cfg, path } = loadConfigWithPath();
console.log('Loaded from:', path);
```

## Functions

### loadConfig

Load and validate configuration from a YAML file.

```typescript theme={null}
loadConfig(configPath?: string): OrchestratorConfig
```

<ParamField path="configPath" type="string">
  Explicit path to config file. If omitted, searches standard locations.
</ParamField>

**Returns:**

<ResponseField name="OrchestratorConfig" type="object">
  Validated and normalized configuration object.
</ResponseField>

**Throws:**

* `Error("No agent-orchestrator.yaml found")` - No config file found in search locations
* `ZodError` - Config validation failed

**Example:**

```typescript theme={null}
try {
  const config = loadConfig();
  console.log('Projects:', Object.keys(config.projects));
  console.log('Defaults:', config.defaults);
} catch (err) {
  if (err instanceof ZodError) {
    console.error('Config validation failed:');
    for (const issue of err.issues) {
      console.error(`  ${issue.path.join('.')}: ${issue.message}`);
    }
  } else {
    console.error('Config not found:', err.message);
  }
}
```

***

### loadConfigWithPath

Load config and return both the config object and resolved file path.

```typescript theme={null}
loadConfigWithPath(configPath?: string): { config: OrchestratorConfig; path: string }
```

<ParamField path="configPath" type="string">
  Explicit path to config file. If omitted, searches standard locations.
</ParamField>

**Returns:**

<ResponseField name="config" type="OrchestratorConfig">
  Validated configuration object.
</ResponseField>

<ResponseField name="path" type="string">
  Absolute path to the loaded config file.
</ResponseField>

**Example:**

```typescript theme={null}
const { config, path } = loadConfigWithPath();
console.log(`Loaded from: ${path}`);
console.log(`Config hash: ${generateConfigHash(path)}`);
```

***

### findConfig

Find the config file path without loading it.

```typescript theme={null}
findConfig(startDir?: string): string | null
```

<ParamField path="startDir" type="string">
  Directory to start searching from. Defaults to CWD.
</ParamField>

**Returns:**

<ResponseField name="string | null" type="string">
  Absolute path to config file, or null if not found.
</ResponseField>

**Example:**

```typescript theme={null}
const configPath = findConfig();
if (configPath) {
  console.log('Config found at:', configPath);
} else {
  console.log('No config file found. Run `ao init`.');
}
```

***

### validateConfig

Validate a raw config object without loading from file.

```typescript theme={null}
validateConfig(raw: unknown): OrchestratorConfig
```

<ParamField path="raw" type="unknown" required>
  Raw config object (from YAML parse, JSON, etc.).
</ParamField>

**Returns:**

<ResponseField name="OrchestratorConfig" type="object">
  Validated and normalized configuration.
</ResponseField>

**Example:**

```typescript theme={null}
import { parse as parseYaml } from 'yaml';
import { validateConfig } from '@composio/ao-core';

const yaml = `
projects:
  my-app:
    repo: org/repo
    path: ~/my-app
`;

const raw = parseYaml(yaml);
const config = validateConfig(raw);
```

***

### getDefaultConfig

Get a default config object (useful for `ao init`).

```typescript theme={null}
getDefaultConfig(): OrchestratorConfig
```

**Returns:**

<ResponseField name="OrchestratorConfig" type="object">
  Config with all defaults and empty projects.
</ResponseField>

**Example:**

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

const defaultConfig = getDefaultConfig();
const yaml = stringify(defaultConfig);
writeFileSync('agent-orchestrator.yaml', yaml);
```

## Config Search Order

The config loader searches these locations in order:

1. **`AO_CONFIG_PATH` environment variable** (if set)
   ```bash theme={null}
   export AO_CONFIG_PATH=~/custom/config.yaml
   ```

2. **Search up directory tree from CWD** (like git)
   ```
   /Users/foo/projects/my-app/agent-orchestrator.yaml
   /Users/foo/projects/agent-orchestrator.yaml
   /Users/foo/agent-orchestrator.yaml
   ...
   ```

3. **Explicit `startDir` parameter** (if provided)
   ```typescript theme={null}
   findConfig('/path/to/start');
   ```

4. **Home directory locations**
   * `~/.agent-orchestrator.yaml`
   * `~/.agent-orchestrator.yml`
   * `~/.config/agent-orchestrator/config.yaml`

<Note>
  Both `.yaml` and `.yml` extensions are supported. The loader tries `.yaml` first, then `.yml`.
</Note>

## Configuration Schema

### Top-Level Config

```typescript theme={null}
interface OrchestratorConfig {
  configPath: string;  // Set automatically during load
  port?: number;       // Web dashboard port (default: 3000)
  terminalPort?: number;  // Terminal WebSocket port (default: 3001)
  directTerminalPort?: number;  // Direct terminal WS port (default: 3003)
  readyThresholdMs: number;  // "ready" → "idle" threshold (default: 300000)
  defaults: DefaultPlugins;
  projects: Record<string, ProjectConfig>;
  notifiers: Record<string, NotifierConfig>;
  notificationRouting: Record<EventPriority, string[]>;
  reactions: Record<string, ReactionConfig>;
}
```

**Example:**

```yaml theme={null}
port: 3000
terminalPort: 3001
readyThresholdMs: 300000  # 5 minutes

defaults:
  runtime: tmux
  agent: claude-code
  workspace: worktree
  notifiers: [composio, desktop]

projects:
  my-app:
    repo: org/repo
    path: ~/my-app

notificationRouting:
  urgent: [desktop, composio]
  action: [desktop, composio]
  warning: [composio]
  info: [composio]

reactions:
  ci-failed:
    auto: true
    action: send-to-agent
    message: "CI is failing. Run `gh pr checks` and fix."
```

### DefaultPlugins

```typescript theme={null}
interface DefaultPlugins {
  runtime: string;      // Default: "tmux"
  agent: string;        // Default: "claude-code"
  workspace: string;    // Default: "worktree"
  notifiers: string[];  // Default: ["composio", "desktop"]
}
```

### ProjectConfig

```typescript theme={null}
interface ProjectConfig {
  name: string;           // Display name (defaults to config key)
  repo: string;           // Required: "owner/repo"
  path: string;           // Required: ~/path/to/project
  defaultBranch: string;  // Default: "main"
  sessionPrefix: string;  // Default: auto-generated from path
  runtime?: string;       // Override default runtime
  agent?: string;         // Override default agent
  workspace?: string;     // Override default workspace
  tracker?: TrackerConfig;
  scm?: SCMConfig;
  symlinks?: string[];    // Files to symlink into workspaces
  postCreate?: string[];  // Commands to run after workspace creation
  agentConfig?: AgentSpecificConfig;
  reactions?: Record<string, Partial<ReactionConfig>>;
  agentRules?: string;    // Inline rules for agents
  agentRulesFile?: string;  // Path to rules file
  orchestratorRules?: string;  // Rules for orchestrator (reserved)
}
```

**Example:**

```yaml theme={null}
projects:
  my-app:
    name: "My Application"
    repo: org/my-app
    path: ~/projects/my-app
    defaultBranch: main
    sessionPrefix: app  # Sessions: app-1, app-2, ...
    
    # Plugin overrides
    runtime: tmux
    agent: claude-code
    workspace: worktree
    
    # Tracker config
    tracker:
      plugin: linear
      teamId: "abc123"
    
    # SCM config
    scm:
      plugin: github
    
    # Workspace setup
    symlinks:
      - .env
      - node_modules
    postCreate:
      - pnpm install
    
    # Agent config
    agentConfig:
      permissions: skip
      model: claude-4.5-sonnet
    
    # Reaction overrides
    reactions:
      ci-failed:
        retries: 3  # Override default
    
    # Agent rules
    agentRulesFile: .claude/rules.md
```

### ReactionConfig

```typescript theme={null}
interface ReactionConfig {
  auto: boolean;              // Default: true
  action: 'send-to-agent' | 'notify' | 'auto-merge';
  message?: string;           // For send-to-agent
  priority?: EventPriority;   // For notify
  retries?: number;           // Max send-to-agent retries
  escalateAfter?: number | string;  // Attempts or duration
  threshold?: string;         // Duration trigger (e.g., "10m")
  includeSummary?: boolean;   // Include session summary in notification
}
```

**Example:**

```yaml theme={null}
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
  
  changes-requested:
    auto: true
    action: send-to-agent
    message: "Review comments found. Check with `gh pr view --comments`."
    escalateAfter: "30m"
  
  approved-and-green:
    auto: false
    action: notify
    priority: action
```

### TrackerConfig

```typescript theme={null}
interface TrackerConfig {
  plugin: string;  // "github" | "linear" | custom
  [key: string]: unknown;  // Plugin-specific config
}
```

**Example:**

```yaml theme={null}
tracker:
  plugin: linear
  teamId: "abc123"
  apiKey: "${LINEAR_API_KEY}"  # From environment
```

### NotifierConfig

```typescript theme={null}
interface NotifierConfig {
  plugin: string;  // "desktop" | "slack" | "webhook" | custom
  [key: string]: unknown;  // Plugin-specific config
}
```

**Example:**

```yaml theme={null}
notifiers:
  slack:
    plugin: slack
    webhookUrl: "${SLACK_WEBHOOK_URL}"
    channel: "#eng-notifications"
  
  webhook:
    plugin: webhook
    url: "https://example.com/webhook"
    headers:
      Authorization: "Bearer ${API_TOKEN}"
```

## Validation

The config loader performs extensive validation:

### 1. Schema Validation (Zod)

```typescript theme={null}
const OrchestratorConfigSchema = z.object({
  port: z.number().default(3000),
  readyThresholdMs: z.number().nonnegative().default(300_000),
  defaults: DefaultPluginsSchema.default({}),
  projects: z.record(ProjectConfigSchema),
  // ...
});
```

**Example error:**

```
Validation error at "projects.my-app.repo": Required
Validation error at "projects.my-app.path": Required
```

### 2. Project Uniqueness

Prevents duplicate project IDs (directory basenames):

```typescript theme={null}
if (projectIds.has(projectId)) {
  throw new Error(
    `Duplicate project ID detected: "${projectId}"\n` +
    `Multiple projects have the same directory basename.`
  );
}
```

**Example:**

```yaml theme={null}
# ERROR: Both projects have basename "my-app"
projects:
  app1:
    path: ~/projects/my-app
  app2:
    path: ~/work/my-app
```

### 3. Session Prefix Collisions

Prevents duplicate session prefixes:

```typescript theme={null}
if (prefixes.has(prefix)) {
  throw new Error(
    `Duplicate session prefix detected: "${prefix}"\n` +
    `Add explicit sessionPrefix to one of these projects.`
  );
}
```

**Fix:**

```yaml theme={null}
projects:
  app1:
    path: ~/projects/my-app
    sessionPrefix: app1  # Explicit prefix
  app2:
    path: ~/work/my-app
    sessionPrefix: app2  # Explicit prefix
```

## Defaults

The config loader applies intelligent defaults:

### 1. Plugin Defaults

```typescript theme={null}
defaults: {
  runtime: "tmux",
  agent: "claude-code",
  workspace: "worktree",
  notifiers: ["composio", "desktop"],
}
```

### 2. Project Defaults

```typescript theme={null}
// Name from config key
if (!project.name) {
  project.name = configKey;
}

// Session prefix from path basename
if (!project.sessionPrefix) {
  const projectId = basename(project.path);
  project.sessionPrefix = generateSessionPrefix(projectId);
}

// Infer SCM from repo
if (!project.scm && project.repo.includes('/')) {
  project.scm = { plugin: 'github' };
}

// Default tracker to GitHub issues
if (!project.tracker) {
  project.tracker = { plugin: 'github' };
}
```

### 3. Notification Routing Defaults

```typescript theme={null}
notificationRouting: {
  urgent: ['desktop', 'composio'],
  action: ['desktop', 'composio'],
  warning: ['composio'],
  info: ['composio'],
}
```

### 4. Reaction Defaults

See [Reaction System](/api/lifecycle-manager#reaction-system) for default reactions.

## Path Expansion

All path fields are expanded:

```typescript theme={null}
function expandHome(filepath: string): string {
  if (filepath.startsWith('~/')) {
    return join(homedir(), filepath.slice(2));
  }
  return filepath;
}

for (const project of Object.values(config.projects)) {
  project.path = expandHome(project.path);
}
```

**Example:**

```yaml theme={null}
# Input
projects:
  my-app:
    path: ~/projects/my-app

# After expansion (on macOS/Linux)
projects:
  my-app:
    path: /Users/foo/projects/my-app
```

## Minimal Config

You only need to specify projects - everything else has defaults:

```yaml theme={null}
projects:
  my-app:
    repo: org/repo
    path: ~/my-app
```

This expands to:

```yaml theme={null}
port: 3000
readyThresholdMs: 300000

defaults:
  runtime: tmux
  agent: claude-code
  workspace: worktree
  notifiers: [composio, desktop]

projects:
  my-app:
    name: my-app
    repo: org/repo
    path: /Users/foo/my-app
    defaultBranch: main
    sessionPrefix: my-app
    tracker:
      plugin: github
    scm:
      plugin: github
    agentConfig:
      permissions: skip

notifiers: {}

notificationRouting:
  urgent: [desktop, composio]
  action: [desktop, composio]
  warning: [composio]
  info: [composio]

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
  # ... (all default reactions)
```

## Complete Example

```typescript theme={null}
import { loadConfig, findConfig } from '@composio/ao-core';
import { ZodError } from 'zod';

// Find config file
const configPath = findConfig();
if (!configPath) {
  console.error('No config file found. Run `ao init`.');
  process.exit(1);
}

console.log(`Using config: ${configPath}`);

// Load and validate
try {
  const config = loadConfig(configPath);
  
  // Display projects
  console.log('\nProjects:');
  for (const [id, project] of Object.entries(config.projects)) {
    console.log(`  ${id}:`);
    console.log(`    Name: ${project.name}`);
    console.log(`    Repo: ${project.repo}`);
    console.log(`    Path: ${project.path}`);
    console.log(`    Prefix: ${project.sessionPrefix}`);
    console.log(`    Agent: ${project.agent ?? config.defaults.agent}`);
  }
  
  // Display defaults
  console.log('\nDefaults:');
  console.log(`  Runtime: ${config.defaults.runtime}`);
  console.log(`  Agent: ${config.defaults.agent}`);
  console.log(`  Workspace: ${config.defaults.workspace}`);
  console.log(`  Notifiers: ${config.defaults.notifiers.join(', ')}`);
  
  // Display reactions
  console.log('\nReactions:');
  for (const [key, reaction] of Object.entries(config.reactions)) {
    console.log(`  ${key}: ${reaction.action} (auto: ${reaction.auto})`);
  }
  
} catch (err) {
  if (err instanceof ZodError) {
    console.error('\nConfig validation failed:');
    for (const issue of err.issues) {
      console.error(`  ${issue.path.join('.')}: ${issue.message}`);
    }
  } else {
    console.error('Failed to load config:', err);
  }
  process.exit(1);
}
```

## See Also

* [Lifecycle Manager](/api/lifecycle-manager) - Uses config for reactions and notification routing
* [Session Manager](/api/session-manager) - Uses config for project and plugin resolution
* [Plugin Registry](/plugins/overview) - Loads plugins based on config


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