← Back to Plugins
Voice

Auto Session Title

daropotter By daropotter 👁 7 views ▲ 0 votes

OpenClaw plugin that automatically labels sessions with a short title generated from the first user prompt.

GitHub

Install

npm install      #

Configuration Example

{
  "plugins": {
    "load": {
      "paths": ["/ABSOLUTE/PATH/TO/auto-session-title"]
    }
  }
}

README

# Auto Session Title — OpenClaw plugin

Automatically gives your OpenClaw sessions short, human‑readable titles based on
the first user prompt. Instead of technical identifiers
(`agent:main:main`, `session:local:7f3a9c…`) your session list shows things like
**“Session naming in OpenClaw”** or **“Avalonia UI binding issue”**.

Titles are always generated in **English**, regardless of the conversation
language.

The plugin works entirely through the official OpenClaw plugin API — it does not
modify the core and never edits `sessions.json` by hand.

## Features

- Auto‑labels a session after its first successful agent run.
- Never overwrites a manually set (or previously generated) label, unless you
  opt in via `overwriteExisting`.
- One LLM call per session at most — no extra cost once a label exists.
- Sends only a minimal, sanitized slice of the conversation to the model;
  strips code/logs and redacts obvious secrets before sending.
- Rejects junk titles (technical ids, secrets, raw JSON, too short).
- Optional cheaper/dedicated model just for titles.

## How it works

```
User sends the first prompt
        ↓
Agent finishes the run  →  `agent_end` hook
        ↓
Plugin checks whether the session already has a label  (if yes → stop)
        ↓
It extracts a minimal, cleaned slice of the conversation
        ↓
It calls the model (api.runtime.llm.complete) asking for a JSON title
        ↓
It validates and cleans the result (sanitize-title)
        ↓
It stores the title as the session `label`
        (api.runtime.agent.session.patchSessionEntry)
```

A title is generated **once per session**: when a label already exists, the
plugin performs no LLM call at all (unless you enable `overwriteExisting`).

## Requirements

- OpenClaw `>= 2026.6.0` (verified on `2026.6.10`).
- Node.js `>= 22` (for building and running tests).

## Installation

### 1. Get the plugin and build it

```bash
git clone https://github.com/daropotter/auto-session-title.git
cd auto-session-title
npm install      # installs TypeScript + OpenClaw types (devDependencies)
npm run build    # compiles src/ → dist/
```

After building, the plugin directory contains: `openclaw.plugin.json`,
`index.js` (a re‑export of `dist/index.js`), and the compiled `dist/`.

### 2. Point OpenClaw at the plugin directory

OpenClaw discovers the plugin by its `openclaw.plugin.json`. Add the plugin
directory to `plugins.load.paths` in your `openclaw.json`:

```json
{
  "plugins": {
    "load": {
      "paths": ["/ABSOLUTE/PATH/TO/auto-session-title"]
    }
  }
}
```

> You can also point at a parent directory that holds several plugins — OpenClaw
> scans subdirectories for `openclaw.plugin.json`.

### 3. Enable the plugin and grant conversation access

The plugin reads conversation content, so it **requires explicit opt‑in** via
`hooks.allowConversationAccess`. Without it, the OpenClaw host will not deliver
message content to the `agent_end` hook and the plugin does nothing. This is an
OpenClaw security gate, not a plugin setting.

```json
{
  "plugins": {
    "entries": {
      "auto-session-title": {
        "enabled": true,
        "hooks": {
          "allowConversationAccess": true
        },
        "config": {
          "maxTitleChars": 60,
          "overwriteExisting": false,
          "minMessages": 1
        }
      }
    }
  }
}
```

### 4. Restart OpenClaw

After a restart, new sessions are labeled automatically once the first agent run
completes.

## Configuration

All fields go under `plugins.entries["auto-session-title"].config`:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `maxTitleChars` | number | `60` | Maximum title length (10–200). |
| `minMessages` | number | `1` | Minimum number of user messages before a title is generated. |
| `overwriteExisting` | boolean | `false` | Whether to overwrite an existing label. |
| `includeAssistantReply` | boolean | `true` | Include the first assistant reply as extra context. |
| `maxContextChars` | number | `4000` | Character cap on the context sent to the model (200–20000). |
| `model` | string | — | Optional cheaper model ref (see below). |
| `temperature` | number | `0.3` | Model temperature (0–2). |
| `purpose` | string | `auto-session-title.generate` | Audit label passed to `llm.complete`. |
| `liveRefresh` | boolean | `true` | After labeling, emit `sessions.changed` so the Control UI updates the title immediately (no click). Best‑effort. |

Unknown/invalid values are ignored (fall back to defaults) and numeric fields
are clamped to their valid ranges.

### Cheaper/dedicated model for titles (optional)

By default the plugin uses the model configured for the agent. To use a cheaper
model just for titles, the host must allow it via `llm.allowModelOverride` +
`llm.allowedModels`, and you set `config.model`:

```json
{
  "plugins": {
    "entries": {
      "auto-session-title": {
        "llm": {
          "allowModelOverride": true,
          "allowedModels": ["openai/gpt-4.1-mini"]
        },
        "config": {
          "model": "openai/gpt-4.1-mini"
        }
      }
    }
  }
}
```

## Instant UI refresh (`liveRefresh`)

The Control UI updates session titles from `sessions.changed` events. The
official host path is `sessions.patch` → `emitSessionsChanged` (full session row
snapshot, `reason: "patch"`). A bare `patchSessionEntry` from the plugin SDK
does **not** emit that broadcast, so the sidebar keeps the old title until the
next `sessions.list` reload (e.g. switching chats).

When `liveRefresh` is enabled (the default), the plugin:

1. **Tries in-process `sessions.patch`** through the host gateway dispatcher
   (same code path as a manual label edit in the UI). This persists the label
   and broadcasts `sessions.changed` in one step.
2. **Falls back** to `patchSessionEntry` plus a lifecycle `sessions.changed`
   emit (with `sessionId` when available) if the dispatcher is unreachable.

Both mechanisms are **best-effort and fully defensive**: host chunks are detected
at runtime from the gateway `dist/` directory; any failure only disables the
live refresh — the title write still succeeds. Set `liveRefresh: false` to skip
refresh attempts entirely.

Log line after labeling: `persist=gateway|store`, `liveRefresh=true|false`,
`via=gateway-patch|lifecycle|none`.

## Security & privacy

- Only a **minimal slice** of the conversation is sent to the model (the first
  prompt and optionally the first reply), truncated to `maxContextChars`.
- Code blocks and log lines are stripped, and detected secrets (API keys,
  tokens, JWTs, passwords, private keys) are redacted before sending.
- A title that looks like a technical id, a secret, raw JSON, or is too short is
  rejected and never stored.
- Conversation access requires an explicit `hooks.allowConversationAccess: true`.

## Development

```bash
npm run build       # compile TypeScript → dist/
npm run typecheck   # tsc --noEmit (checks compatibility with the OpenClaw SDK)
npm test            # build + node --test (unit and integration tests)
npm run clean       # remove dist/
```

Tests run against the compiled `dist/` and do not require a running OpenClaw
instance:

- `sanitize-title` — title cleanup and validation,
- `config` — configuration normalization,
- `messages` — context extraction and secret redaction,
- `title-generator` — prompt building and parsing the model response,
- `session-labeler` — reading/writing the label,
- `integration` — the full `agent_end` hook flow against the built plugin entry.

## Architecture

```
auto-session-title/
├── openclaw.plugin.json     # plugin manifest (id, configSchema, uiHints)
├── package.json
├── tsconfig.json
├── index.js                 # entry: re-export of dist/index.js
├── src/
│   ├── index.ts             # registers the agent_end hook, orchestration
│   ├── config.ts            # config normalization + defaults
│   ├── messages.ts          # context extraction/cleanup
│   ├── title-generator.ts   # prompt + model call + JSON parsing
│   ├── session-labeler.ts   # read/write the session label
│   └── sanitize-title.ts    # title validation and cleanup
├── test/                    # node --test suites
└── dist/                    # build output (generated)
```

OpenClaw APIs used (verified on `[email protected]`):

- `definePluginEntry` from `openclaw/plugin-sdk/core`,
- the `api.on("agent_end", (event, ctx) => …)` hook,
- `api.runtime.agent.session.getSessionEntry` / `patchSessionEntry`,
- `api.runtime.llm.complete(...)`.

## Compatibility note

Hook names, event shapes, and import paths can differ between OpenClaw versions.
The plugin checks at runtime that the APIs it needs (`session`, `llm.complete`)
are available and, if they are missing, quietly skips labeling (logging a
`warn`) instead of breaking the agent run. After an SDK upgrade, run
`npm run typecheck` to catch changes.

## License

[MIT](LICENSE) © Daro Potter
voice

Comments

Sign in to leave a comment

Loading comments...