> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thecontext.company/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenClaw

> Set up AI agent observability for OpenClaw apps with automatic tracing of LLM calls, tool executions, and agent lifecycle events.

## Set TCC environment variables

<Tip>Our SDKs default to using the `TCC_API_KEY` environment variable.</Tip>

```bash .env theme={null}
TCC_API_KEY="your-api-key"
```

## Instrument OpenClaw

<Tabs>
  <Tab title="Plugin install">
    The fastest way to get started. Install the plugin and configure it in your `openclaw.json`.

    #### Step 1: Install the plugin

    ```bash theme={null}
    openclaw plugins install @contextcompany/openclaw
    ```

    #### Step 2: Configure the plugin

    Add the plugin to your `openclaw.json`:

    ```json openclaw.json theme={null}
    {
      "plugins": {
        "allow": ["@contextcompany/openclaw"],
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}"
            }
          }
        }
      }
    }
    ```

    #### Step 3: Restart the gateway

    ```bash theme={null}
    openclaw gateway restart
    ```

    That's it! The plugin hooks into the agent runtime and starts sending traces to The Context Company. All LLM calls, tool executions, and agent lifecycle events are automatically captured.
  </Tab>

  <Tab title="Manual registration">
    Use manual registration when you need per-run metadata, custom run IDs, or feedback wiring via `onRunStart` / `onRunEnd` callbacks.

    #### Step 1: Install the package

    ```bash theme={null}
    npm i @contextcompany/openclaw
    ```

    #### Step 2: Register in a custom extension

    Create an extension that calls `register()` with the OpenClaw plugin API:

    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api);
    }
    ```

    You can also pass explicit configuration:

    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        apiKey: "tcc_...",
        debug: true,
      });
    }
    ```

    #### Step 3: Restart the gateway

    ```bash theme={null}
    openclaw gateway restart
    ```
  </Tab>
</Tabs>

## Adding custom metadata

Custom metadata allows you to add additional properties to your [agent runs](/concepts#runs).

This is particularly useful for tying agent runs to your own specific business logic, letting you filter and analyze agent runs by user, organization, feature, or some other dimension.

<Tabs>
  <Tab title="Plugin config">
    Set static metadata in your `openclaw.json` to attach the same metadata to every run:

    ```json openclaw.json theme={null}
    {
      "plugins": {
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}",
              "metadata": {
                "environment": "production",
                "service": "support-bot"
              }
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Manual registration">
    Derive metadata from the run context by passing a function to the `metadata` config:

    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        metadata: (ctx) => ({
          agent: ctx.agentId ?? "unknown",
          trigger: ctx.trigger ?? "user",
          channel: ctx.channelId ?? "none",
        }),
      });
    }
    ```

    For per-run metadata that depends on the prompt or needs to be set imperatively, use the `onRunStart` callback:

    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        onRunStart: ({ ctx, prompt, setMetadata }) => {
          setMetadata({
            agent: ctx.agentId ?? "unknown",
            trigger: ctx.trigger ?? "user",
            promptPreview: prompt.slice(0, 120),
          });
        },
      });
    }
    ```
  </Tab>
</Tabs>

Agent runs are automatically indexed by your custom metadata fields and can be filtered directly in the dashboard.

<Note>
  The `tcc.*` namespace is reserved. Only the [reserved TCC metadata keys](/concepts#tcc-metadata-keys) (`tcc.runId`, `tcc.sessionId`, `tcc.conversational`, `tcc.agent`, `tcc.userId`, `tcc.userName`, `tcc.orgId`, `tcc.orgName`) are recognized; any other `tcc.*` keys are ignored. None of them appear in your custom metadata.

  The plugin auto-mints a per-run `tcc.runId` and stamps it on every payload. Do not set `tcc.runId` in metadata. Use the `runId` config (consumed once) or override per run via the `onRunStart` callback.
</Note>

## Adding user feedback

User feedback allows you to collect score (thumbs up & thumbs down) and text feedback (up to 2000 characters) from end users on your [agent runs](/concepts#runs).

This is useful for tracking user satisfaction, identifying problematic responses, and filtering agent runs in the dashboard to focus on positive or negative feedback.

### Step 1: Capture the run ID

The plugin auto-generates a unique run ID (UUID) for every agent turn. Use the `onRunEnd` callback to capture it:

```typescript extensions/tcc-observability/index.ts theme={null}
import { register, submitFeedback } from "@contextcompany/openclaw";

const runIdByConversation = new Map<string, string>();

export default async function (api) {
  const handle = register(api, {
    onRunEnd: ({ runId, ctx }) => {
      // Stash the run ID by conversation for later feedback
      if (ctx.sessionKey) {
        runIdByConversation.set(ctx.sessionKey, runId);
      }
    },
  });
}

// Export for use in other extensions (e.g. reaction handlers)
export function getRunIdForConversation(sessionKey: string): string | undefined {
  return runIdByConversation.get(sessionKey);
}
```

You can also retrieve the run ID directly from the handle:

```typescript theme={null}
// Get the most recent run ID
const runId = handle.getRunId();

// Get the run ID for a specific session (concurrency-safe)
const runId = handle.getRunIdForSession(sessionKey);
```

### Step 2: Submit feedback

When the user provides feedback, submit it using the `submitFeedback` function.

Both `score` and `text` are optional individually, but each request must include at least one of them:

`score` is the thumbs rating. Use only `"thumbs_up"` or `"thumbs_down"`.

`text` is written feedback from your user, up to 2000 characters.

```typescript theme={null}
import { submitFeedback } from "@contextcompany/openclaw";

// Submit score and/or text feedback:
await submitFeedback({
  runId: runId,          // The run ID from your agent execution
  score: "thumbs_up",   // Optional thumbs rating: "thumbs_up" or "thumbs_down"
  text: "Great response!", // Optional written user feedback, up to 2000 characters
});
```

Agent runs with feedback can be filtered in the dashboard using the feedback filter.

## Tracking agent sessions

[Agent sessions](/concepts#sessions) represent multiple agent runs that are grouped together. The most common use case is tracking entire conversations between a human user and an AI agent in chatbot interfaces.

By default, the plugin automatically uses OpenClaw's `ctx.sessionKey` as the session ID, which is already scoped per conversation thread. This means sessions are tracked automatically for channel-based setups (e.g. Slack threads).

To override this default, use manual registration and derive the session ID from the run context:

```typescript extensions/tcc-observability/index.ts theme={null}
import { register } from "@contextcompany/openclaw";

export default async function (api) {
  register(api, {
    sessionId: (ctx) => ctx.channelId ?? ctx.sessionKey,
  });
}
```

You can also override the session ID per-run using the `onRunStart` callback:

```typescript extensions/tcc-observability/index.ts theme={null}
import { register } from "@contextcompany/openclaw";

export default async function (api) {
  register(api, {
    onRunStart: ({ ctx, setSessionId }) => {
      setSessionId(ctx.channelId ?? "default-session");
    },
  });
}
```

The value of `sessionId` should be a unique identifier for the agent session. This can be any string, but it's generally recommended to use a UUID.

Agent sessions are automatically indexed and can be filtered directly in the dashboard.

## Marking runs as conversational

A [conversational run](/concepts#conversational-runs) is an agent run that was initiated by a user. Marking a run as conversational tells The Context Company that this run involves direct user interaction.

This is important because conversational runs are the only runs monitored for user insights, such as user confusion, frustration, or any other custom insights you want to track. Runs that are not marked as conversational (e.g. background jobs, cron tasks, or internal automations) are excluded from user insight analysis.

Mark a run as conversational by setting `tcc.conversational` to `"true"` in metadata:

<Tabs>
  <Tab title="Plugin config">
    ```json openclaw.json theme={null}
    {
      "plugins": {
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}",
              "metadata": {
                "tcc.conversational": "true"
              }
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Manual registration">
    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        metadata: {
          "tcc.conversational": "true",
        },
      });
    }
    ```

    Or set it per-run using the `onRunStart` callback:

    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        onRunStart: ({ ctx, setMetadata }) => {
          // Only mark user-initiated runs as conversational
          if (ctx.trigger === "user") {
            setMetadata({ "tcc.conversational": "true" });
          }
        },
      });
    }
    ```
  </Tab>
</Tabs>

## Identifying the agent

If your product ships more than one named agent, set the reserved `tcc.agent` metadata key to scope the run to a specific [agent](/concepts#agents). The dashboard's top-level agent selector, per-agent patterns and recaps, and the `agent` filter on the [REST API](/access-data/api) and [MCP tools](/access-data/mcp) all read from this key.

<Tabs>
  <Tab title="Plugin config">
    ```json openclaw.json theme={null}
    {
      "plugins": {
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}",
              "metadata": {
                "tcc.agent": "support-agent"
              }
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Per-run via onRunStart">
    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        onRunStart: ({ ctx, setMetadata }) => {
          setMetadata({
            "tcc.agent": ctx.agentName,
          });
        },
      });
    }
    ```
  </Tab>
</Tabs>

<Note>
  Agent names that collide with reserved dashboard routes (for example `runs`, `sessions`, `patterns`, `recaps`, `overview`, `search`, `failures`, `feedback`, `tools`, `topics`, `views`, `settings`, `mcp-and-api`) are dropped.
</Note>

## Identifying users and organizations

Attach the end user and their organization to a run as first-class identity using the reserved `tcc.userId`, `tcc.userName`, `tcc.orgId`, and `tcc.orgName` metadata keys. This is **not the same** as adding a `userId` field to custom metadata — these keys promote user and org identity to dedicated dashboard filters and unlock native user/org search, per-user views, and per-org analytics. See [User and organization identity](/concepts#user-and-organization-identity) for the full concept.

Set these whenever you have a stable identifier for the end user or their organization in your product.

<Tabs>
  <Tab title="Plugin config">
    ```json openclaw.json theme={null}
    {
      "plugins": {
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}",
              "metadata": {
                "tcc.userId": "user-123",
                "tcc.userName": "Jane Doe",
                "tcc.orgId": "org-456",
                "tcc.orgName": "Acme Inc."
              }
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Per-run via onRunStart">
    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        onRunStart: ({ ctx, setMetadata }) => {
          // Look up the user/org for this run from your context
          setMetadata({
            "tcc.userId": ctx.userId,
            "tcc.userName": ctx.userName,
            "tcc.orgId": ctx.orgId,
            "tcc.orgName": ctx.orgName,
          });
        },
      });
    }
    ```
  </Tab>
</Tabs>

<Note>
  `tcc.userName` and `tcc.orgName` require the corresponding ID (`tcc.userId` / `tcc.orgId`) to also be set. Names without IDs are dropped.
</Note>

## Lifecycle callbacks

The `onRunStart` and `onRunEnd` callbacks give you fine-grained control over each agent run. These are only available with manual registration.

### `onRunStart`

Called at the start of each agent run, before any LLM calls. Use it to set per-run metadata, override the run ID, or customize the session ID.

```typescript extensions/tcc-observability/index.ts theme={null}
import { register } from "@contextcompany/openclaw";

export default async function (api) {
  register(api, {
    onRunStart: ({ runId, ctx, prompt, setRunId, setSessionId, setMetadata }) => {
      // runId: the auto-generated UUID for this run
      // ctx: { agentId, sessionKey, channelId, trigger, ... }
      // prompt: the user's input text

      // Override the run ID if needed
      setRunId("my-custom-uuid");

      // Override the session ID
      setSessionId("my-session-id");

      // Attach per-run metadata
      setMetadata({
        agent: ctx.agentId ?? "unknown",
        trigger: ctx.trigger ?? "user",
      });
    },
  });
}
```

### `onRunEnd`

Called when an agent run completes. Use it to capture the run ID for feedback, log results, or trigger downstream actions.

```typescript extensions/tcc-observability/index.ts theme={null}
import { register } from "@contextcompany/openclaw";

export default async function (api) {
  register(api, {
    onRunEnd: ({ runId, ctx, success, sessionId, metadata }) => {
      // runId: the final run ID
      // ctx: { agentId, sessionKey, channelId, trigger, ... }
      // success: whether the run completed without errors
      // sessionId: the session ID used for this run
      // metadata: the full merged metadata for this run

      console.log(`Run ${runId} completed (success: ${success})`);
    },
  });
}
```

## Configuration

| Option       | Environment variable | Default             | Description                                      |
| ------------ | -------------------- | ------------------- | ------------------------------------------------ |
| `apiKey`     | `TCC_API_KEY`        | —                   | Your Context Company API key                     |
| `debug`      | `TCC_DEBUG`          | `false`             | Enable debug logging                             |
| `sessionId`  | —                    | `ctx.sessionKey`    | Session ID for grouping runs                     |
| `metadata`   | —                    | `{}`                | Key-value metadata attached to every run         |
| `runId`      | —                    | Auto-generated UUID | Explicit run ID (first run only)                 |
| `onRunStart` | —                    | —                   | Callback at run start (manual registration only) |
| `onRunEnd`   | —                    | —                   | Callback at run end (manual registration only)   |

Events are batched and sent when the agent run completes. Sessions that never complete are automatically flushed after 30 minutes.

## Debug mode

You can enable debug mode to log hook events and transport details to the console.

<Tabs>
  <Tab title="Plugin config">
    ```json openclaw.json theme={null}
    {
      "plugins": {
        "entries": {
          "@contextcompany/openclaw": {
            "enabled": true,
            "config": {
              "apiKey": "${TCC_API_KEY}",
              "debug": true
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Manual registration">
    ```typescript extensions/tcc-observability/index.ts theme={null}
    import { register } from "@contextcompany/openclaw";

    export default async function (api) {
      register(api, {
        debug: true,
      });
    }
    ```
  </Tab>

  <Tab title="Environment variable">
    ```bash .env theme={null}
    TCC_DEBUG=true
    ```
  </Tab>
</Tabs>

## Examples

See our [examples repository](https://github.com/The-Context-Company/examples) for more detailed usage examples.
