CodeDocs Vault

Entry Points & Execution Flow

Entry Points

OpenClaw has four distinct entry points:

1. CLI Entry (openclaw.mjssrc/entry.ts)

The primary user-facing entry point.

openclaw.mjs
  │  (lightweight bootstrap — respawns Node with --disable-warning=ExperimentalWarning)
  ▼
src/entry.ts
  │  1. Set process.title = "openclaw"
  │  2. Install warning filter
  │  3. Normalize environment variables
  │  4. Check if experimental warnings need suppression
  │  5. If yes: respawn Node with the flag → parent exits
  │  6. If no: parse CLI profile args → apply profile env
  │  7. Dynamic import("./cli/run-main.js")
  ▼
src/cli/run-main.ts → runCli(argv)
  │  Calls into the Commander.js program
  ▼
src/cli/program.ts → buildProgram()
  │  Registers all subcommands from src/commands/
  ▼
Commander parses argv → dispatches to command handler

The respawn mechanism exists because --disable-warning=ExperimentalWarning cannot be set via NODE_OPTIONS — it must be a direct Node CLI flag. If not already set, the entry script spawns a child process with the flag and bridges stdin/stdout/signals.

2. Library Entry (src/index.ts)

For programmatic use (import { loadConfig } from "openclaw").

// src/index.ts — the library API
export {
  loadConfig,
  monitorWebChannel,
  createDefaultDeps,
  loadSessionStore, saveSessionStore,
  resolveSessionKey, deriveSessionKey,
  ensureBinary, ensurePortAvailable,
  // ... utility exports
};

Side effects on import:

  1. loadDotEnv({ quiet: true }) — loads .env files
  2. normalizeEnv() — standardizes environment
  3. ensureOpenClawCliOnPath() — adds CLI to PATH
  4. enableConsoleCapture() — wraps console.* into structured logs
  5. assertSupportedRuntime() — fails fast if Node is too old

If the module is the main entry (isMainModule), it also installs error handlers and calls program.parseAsync(argv).

3. Daemon Entry (src/cli/daemon-cli.ts)

For running as a background service.

openclaw daemon start
  → Spawns a detached process running daemon-cli.ts
  → Daemon starts the gateway server
  → Monitors channel health
  → Runs cron jobs
  → Accepts WebSocket connections from clients

4. Plugin SDK Entry (src/plugin-sdk/index.ts)

For third-party plugin development.

// External plugin code:
import { ... } from "openclaw/plugin-sdk";
 
export default async (api: OpenClawPluginApi) => {
  api.registerTool(myTool);
  api.on("beforePromptBuild", handler);
};

Main Code Paths

Path 1: CLI Interactive Session

The most common path — user types openclaw and starts chatting.

buildProgram()
  → default command (no subcommand)
  → starts TUI (Terminal UI)
  → loads config
  → resolves default agent
  → creates agent session
  → enters interactive prompt loop:
      user types message
        → runEmbeddedAttempt(params)
          → builds system prompt
          → creates tools
          → session.prompt(userMessage)
            → [agentic loop]
          → displays streamed response
        → waits for next input

Path 2: Gateway / Daemon Mode

For multi-channel operation.

openclaw daemon start (or openclaw gateway start)
  → starts HTTP + WebSocket server
  → loads config + plugins
  → starts channel monitors (WhatsApp, Telegram, etc.)
  → for each inbound message:
      → channel plugin normalizes message
      → gateway receives event
      → resolveRoute() → agent + session key
      → runEmbeddedAttempt(params)
        → [agentic loop]
      → dispatches response via channel outbound adapter

Path 3: Web Channel

For browser-based chat.

Browser connects to gateway WebSocket
  → talk.* server methods handle conversation
  → messages flow through same agent runner
  → responses streamed back via WebSocket

Path 4: Single Command Execution

For non-interactive use (openclaw exec "summarize this file").

openclaw exec <prompt>
  → single agent run
  → outputs response to stdout
  → exits

State Transitions

Agent Session Lifecycle

                    ┌──────────┐
                    │  IDLE    │
                    └────┬─────┘
                         │ inbound message
                         ▼
                    ┌──────────┐
              ┌─────│ LOADING  │
              │     └────┬─────┘
              │          │ session loaded, prompt built
              │          ▼
              │     ┌──────────┐
              │     │ RUNNING  │ ◄─── session.prompt() active
              │     └────┬─────┘
              │          │
              │     ┌────┴─────────────────────────┐
              │     │                               │
              │     ▼                               ▼
              │ ┌──────────┐                  ┌──────────┐
              │ │ TOOL_USE │                  │STREAMING │
              │ └────┬─────┘                  │ RESPONSE │
              │      │ tool executes          └────┬─────┘
              │      │ result fed back             │
              │      └──► RUNNING ◄────────────────┘
              │          │
              │          │ no more tool_use
              │          ▼
              │     ┌──────────┐
              │     │ COMPLETE │
              │     └────┬─────┘
              │          │ response dispatched
              │          ▼
              │     ┌──────────┐
              └─────│  IDLE    │
                    └──────────┘

Session Persistence States

NEW → first message creates session file
  → ACTIVE: session file + write lock held
  → IDLE: write lock released, file persists
  → COMPACTING: context window too large, auto-compaction triggered
  → REPAIRED: corrupt session file detected and fixed

Gateway Lifecycle

BOOT
  → Load config
  → Load plugins (discovery → manifest → import → init)
  → Set active plugin registry
  → Start HTTP + WebSocket server
  → Start channel monitors
  → Start cron scheduler
  → RUNNING

RUNNING
  → Config file changes detected → HOT_RELOAD
  → Plugin changes detected → PLUGIN_RELOAD
  → Channel health issues → CHANNEL_RESTART

HOT_RELOAD
  → Reload config
  → Refresh plugin registry
  → Restart affected channels
  → Reinitialize hook runners
  → RUNNING

SHUTDOWN
  → Stop channel monitors
  → Stop cron scheduler
  → Close WebSocket connections
  → Close HTTP server

Key State: The runEmbeddedAttempt() Function

This is the central orchestrator (located at src/agents/pi-embedded-runner/run/attempt.ts). A single invocation handles one complete agent run:

runEmbeddedAttempt(params)
  1. Resolve workspace directory
  2. Set up sandbox (if configured)
  3. Load skill entries + apply env overrides
  4. Resolve bootstrap context files (OPENCLAW.md, etc.)
  5. Create tools (bash, read, write, glob, grep, web_search, message, etc.)
  6. Build system prompt (identity + skills + memory + channel + runtime)
  7. Open SessionManager (load/create session file)
  8. Create SettingsManager
  9. Call createAgentSession() from Pi SDK
  10. Subscribe to session events (messages, tools, agent lifecycle)
  11. Set up abort controller + timeout
  12. Resolve images from prompt (if any)
  13. Call session.prompt(effectivePrompt)  ←── THIS DRIVES THE LOOP
  14. Await completion (or abort/timeout)
  15. Collect results: messages, usage, errors, tool metadata
  16. Release session write lock
  17. Return EmbeddedRunAttemptResult

Steps 1-12 are setup. Step 13 is where the actual AI execution happens — the Pi SDK takes over and runs the inner agentic loop (see The Agentic Loop). Steps 14-17 are teardown.