Custom history plugins

Use a local manifest and command to import agent history formats ctx does not support natively.

Most users should start with native imports for supported agents. Use a custom history plugin when a local tool stores useful agent history in a format ctx does not read yet.

A plugin has two parts:

  • ctx-history-plugin.json, a local manifest that names the history source and command;
  • a script or binary that reads the tool's native storage and writes ctx-history-jsonl-v1 JSONL to stdout.

ctx discovers the manifest, runs the command during explicit import or search-time refresh, validates the JSONL stream, imports it into the local SQLite index, and stores the cursor emitted by the plugin. On the next run, ctx passes that cursor back to the command.

Manifest

Place the manifest at $CTX_DATA_ROOT/plugins/<plugin>/ctx-history-plugin.json or point CTX_HISTORY_PLUGIN_PATH at a manifest or directory.

{
  "schema_version": 1,
  "name": "example-agent",
  "display_name": "Example Agent",
  "version": "0.1.0",
  "history_sources": [
    {
      "id": "default",
      "provider_key": "example-agent",
      "source_id": "default",
      "source_format": "example-agent-sqlite-v1",
      "enabled": true,
      "refresh": "auto",
      "command": ["example-agent-to-ctx", "export"],
      "timeout_seconds": 300
    }
  ]
}

The command is an argv array. ctx does not run it through a shell.

ctx sources
ctx import --history-source example-agent/default
ctx import --history-source-manifest ./ctx-history-plugin.json
ctx search "release notes" --history-source example-agent/default

Selectors use plugin/source or provider_key/source_id. Bare names are not accepted because many integrations use ids such as default.

enabled: true lets ctx import --all run the source. refresh: "auto" lets ctx search run the source during the normal pre-search refresh. Use --refresh off when a search should read only the current index.

Command contract

ctx sets runtime environment variables before invoking the command, including:

  • CTX_HISTORY_SOURCE, such as example-agent/default;
  • CTX_HISTORY_PROVIDER_KEY;
  • CTX_HISTORY_SOURCE_ID;
  • CTX_HISTORY_SOURCE_FORMAT;
  • CTX_HISTORY_CURSOR_STREAM;
  • CTX_HISTORY_CURSOR_FILE, when a previous cursor exists;
  • CTX_HISTORY_FULL_RESCAN, 1 or 0.

The command must write only ctx-history-jsonl-v1 JSONL records to stdout. Progress and warnings should go to stderr.

The cursor belongs to the plugin. It can be a byte offset, SQLite row id, JSON map, sync token, or any other string the plugin can use on the next run.

cursor_path = os.environ.get("CTX_HISTORY_CURSOR_FILE")
cursor = read_cursor(cursor_path) if cursor_path else {}

for row in read_new_messages_after(cursor):
    emit_session_or_event(row)

emit_source_cursor({"message_id": latest_message_id})

For the full manifest and stream schema, run:

ctx docs show history-source-plugins
ctx docs show custom-history-import-format

The same docs are in the public repository: