History Source Plugins
August 3, 2026 ยท View on GitHub
History source plugins let local tools export their history into ctx without
ctx owning those tools' storage schemas. The stable 1.0 route is intentionally
narrow: a manifest identifies a durable provider-owned
ctx-history-jsonl-v1 file, and the CLI registers that file with the normal
daemon-owned Core refresh path.
ctx does not load plugin code in-process and does not provide a second history store for plugins. Import copies policy-selected normalized history into Core; the declared provider file remains the input to later refreshes. Command-only exporters are discoverable but unsupported in 1.0 because command stdout is not a durable provider source.
Install And Discover
Put a manifest at one of:
$CTX_DATA_ROOT/plugins/<plugin>/ctx-history-plugin.json;- any directory or manifest file listed in
CTX_HISTORY_PLUGIN_PATH.
ctx sources and ctx sources --format json discover manifests without
executing commands. A durable regular-file source is reported as available,
importable: true, and import_mode: explicit_source_backed. Its
compatibility import mode describes source acquisition. Its history_source is
the filterable provider_key/source_id route identity; plugin_source is the
plugin/source import-selection alias. Command-only compatibility sources are
reported as unsupported and never importable. Invalid manifests are listed as
non-importable history_source_plugin rows with their validation error.
Manifest example:
{
"schema_version": 1,
"name": "example-agent",
"display_name": "Example Agent history",
"version": "1.0.0",
"history_sources": [
{
"id": "default",
"provider_key": "example-agent",
"source_id": "default",
"source_format": "example-agent-jsonl-v1",
"path": "/path/owned/by/example-agent/history.jsonl",
"enabled": true,
"refresh": "manual"
}
]
}
name, id, provider_key, and source_id must be stable lowercase ASCII
identifiers. path may be absolute or relative to the manifest directory and
must identify a regular provider-owned file. Its ctx-history-jsonl-v1 source
record must match the declared provider_key, source_id, and
source_format.
Legacy manifests may still declare a command argv array so users receive a
typed compatibility diagnostic. They are not importable, are never executed by
ctx sources, and are never copied into ctx-owned storage. A source cannot
declare both path and command runtime options.
enabled and refresh remain discovery metadata in the 1.0 explicit route.
They do not opt a plugin into ctx import --all, ctx setup, or automatic
pre-search execution.
Explicit Import
Select exactly one source:
ctx import --history-source example-agent/default
ctx import --history-source-manifest ./ctx-history-plugin.json
Selectors match either plugin/source or provider_key/source_id and must
resolve to exactly one source before ctx executes anything. Bare plugin names,
source ids, and provider keys are rejected because values such as default
are commonly reused.
--history-source-manifest adds a development manifest for the current
command. Without --history-source, the supplied manifest path must resolve to
exactly one source.
The 1.0 plugin route does not execute plugins from ctx import --all or
automatic search refresh. Run an explicit plugin import after configuring its
durable path; the persistent daemon then watches and refreshes the registered
provider source normally. --reset-cursor is invalid because ctx owns no
plugin cursor.
Core Publication
An accepted import has one acquisition and publication path:
- The CLI validates that the selected manifest identifies a regular provider-owned file.
- A bounded header check verifies the
ctx-history-jsonl-v1schema and exact declared source identity without copying the body. - ctx registers that same file as the explicit custom
ctx_history_jsonl_v1source route. - The normal Core-refresh endpoint publishes a fresh Core generation, or returns an authoritative no-op receipt.
Cold imports, appends, rewrites, replacements, and no-ops all use the shared
custom JSONL source-family path. There is no fallback to the old Store database,
synthetic NativePath body, command-output snapshot, or local content pack.
ctx show reads complete policy-selected normalized records from the active
Core/Tantivy generation and does not reopen the provider-owned JSONL file. If a
later import or refresh cannot read or validate that acquisition source, the
last verified Core generation remains available until a successful refresh
publishes a replacement.
After import, --history-source uses the canonical
provider_key/source_id route identity:
ctx search "release notes" --history-source example-agent/default
ctx search "release notes" --provider-key example-agent --source-id default
These filters imply --provider custom; combining them with another provider
is an error. When plugin/source differs from provider_key/source_id, use the
latter for search; the former remains the explicit import selector.
Minimal Durable Source Shape
import json
print(json.dumps({
"record_type": "manifest",
"schema_version": "ctx-history-jsonl-v1",
}))
print(json.dumps({
"record_type": "source",
"source_id": "default",
"provider_key": "example-agent",
"source_format": "example-agent-jsonl-v1",
}))
# Append stable session, event, file_touch, and edge records to the provider file.
The complete record schema is documented in
docs/custom-history-import-format.md.