Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

cisternal

Status: alpha. APIs may change without notice before 1.0.

Cisternal is a shared telemetry substrate and agent-asset export toolkit for the Praxia tool family. It has two parts:

  • Telemetry — a lightweight, non-blocking event pipeline (JSONL export, OTLP export, MCP-tool registration wrapper) for instrumenting Python tools and MCP servers.
  • Agent-asset export — a CLI that takes a registry of MCP tools/commands and emits native plugin/config bundles for downstream coding-agent surfaces: Claude Code, Cursor, GitHub Copilot, Antigravity, OpenCode, Pi, and JCode.

Install

pip install cisternal

For OTLP export support:

pip install "cisternal[otlp]"

Telemetry quickstart

import cisternal

cisternal.init()  # log_dir defaults to ~/.cisternal/logs, or env-resolved

with cisternal.span("my.operation", request_id="abc123"):
    do_work()

cisternal.emit_event("my.custom_event", tool="foo")
print(cisternal.status())

Check your effective telemetry configuration from the shell:

cisternal telemetry doctor
cisternal telemetry doctor --json --strict

Registering MCP tools

import cisternal

@cisternal.tool
def my_tool(x: int) -> int:
    return x * 2

registry = cisternal.wire(server, app, adapter=my_adapter)

cisternal.tool is a pure-metadata decorator — it returns the original function unchanged. cisternal.wire() snapshots the registry at call time and registers each tool on a FastMCP server (and optionally a Cyclopts CLI app), returning a WiredRegistry for introspection.

Agent-asset export

A tool's agent plugin (skills, agents, hooks, MCP servers, declared in its .praxia/manifest.toml) reaches a coding agent by one of three paths:

Path 1: install through the tool Path 2: direct surface export Path 3: install from GitHub
Who runs it anyone who has the tool installed the tool's developer, in its repo anyone with read access to the repo
Command <tool> plugin install claude (e.g. bth plugin install claude) cisternal assets export --manifest … --surface <s> --out DIR /plugin marketplace add maraxen/<tool> (or the maraxen/plugins index)
Needs the cisternal CLI no — the tool mounts cisternal's sub-app yes no
Needs the tool installed yes yes (source checkout) no, with [plugin.mcp] launch = "uvx"
Installs into the agent yes (Claude Code) no — writes files only (assets install / publish-shared also register them with Claude) yes (Claude Code)
Surfaces install: claude · export: all seven all seven claude

Path 1 — <tool> plugin install|update

Users of a tool that mounts the sub-app:

bth plugin install claude              # publish + register marketplace + install (user scope)
bth plugin install claude --scope project
bth plugin update claude               # republish + update the installed copy
bth plugin export cursor --out DIR     # files only, any surface
bth plugin info                        # where the bundle and the marketplace resolve from
bth plugin install claude --dry-run    # show what would happen, change nothing

install publishes into the shared marketplace, registers that marketplace with Claude Code if it isn't already, then installs the plugin at --scope. If the plugin is already installed at that scope, install updates it (or does nothing when it is current). Installs at other scopes are left alone. update refuses to run when the plugin isn't installed, and updates every stale install at the scope it was installed at. Both commands report skill and agent copies in ~/.claude/ that shadow the plugin; --prune-shadowed moves them into a backup. Restart Claude Code to load the change. cisternal dogfoods this itself: cisternal plugin install claude.

Tool authors add it in three steps:

  1. Mount the sub-app in the tool's cyclopts CLI:

    from cisternal.plugin import PluginSpec, plugin_app
    
    app.command(plugin_app(PluginSpec(name="bathos", package="bathos", cli="bth")))
    

    PluginSpec also takes registry/imports (merge @cisternal.tool commands, for surfaces that emit them), version (defaults to the installed distribution's version), snapshot and manifest paths.

  2. Freeze the manifest into package data, so wheel installs work without the repo:

    cisternal assets snapshot --manifest .praxia/manifest.toml --out src/bathos/agent_plugin.json
    
    [tool.setuptools.package-data]      # or your build backend's equivalent
    bathos = ["agent_plugin.json"]
    
  3. Keep it in sync in CI: cisternal assets snapshot --manifest .praxia/manifest.toml --out src/bathos/agent_plugin.json --check exits 1 if the snapshot is missing or stale.

The bundle is taken from the first of: --manifest PATH; the tool's own .praxia/manifest.toml above the imported package, for an editable install (it is never searched for from inside site-packages, and must name this plugin); the packaged snapshot. plugin info shows which one won.

Path 2 — direct surface export (cisternal assets …)

From the tool's source checkout, with the cisternal CLI:

cisternal assets export --manifest .praxia/manifest.toml --surface cursor --out ./dist/cursor
cisternal assets export --manifest .praxia/manifest.toml --dry-run      # paths + sha256, writes nothing
cisternal assets inspect  --manifest .praxia/manifest.toml              # JSON load report
cisternal assets validate --manifest .praxia/manifest.toml              # structural + golden checks
cisternal assets publish-shared --manifest .praxia/manifest.toml        # into the shared marketplace (+ refresh)
cisternal assets update-all                                             # republish every enrolled plugin
cisternal assets install --manifest .praxia/manifest.toml               # standalone single-plugin marketplace

assets export only writes files. Nothing loads them until something registers them. assets install makes the bundle its own single-plugin marketplace and registers it with Claude Code. It requires a [plugin.marketplace] table in the manifest:

[plugin.marketplace]
name = "my-plugin-marketplace"

[plugin.marketplace.owner]
name = "Your Name"

publish-shared instead adds the plugin to the shared multi-tool marketplace, the same one Path 1 uses. update-all republishes every plugin there from the repo that last published it. A plugin installed through Path 1 is not rebuilt by update-all; instead it prints that tool's own <tool> plugin update claude, because the tool owns the recipe (version, snapshot vs. checkout).

Path 3 — install straight from GitHub

A tool's repo becomes a one-plugin marketplace once it commits the bundle that plugin repo-bundle generates:

<repo>/
├── .claude-plugin/marketplace.json   # one entry, "source": "./plugin"
└── plugin/                           # the Claude bundle; version = <release>+<content digest>
bth plugin repo-bundle            # (re)write plugin/ and .claude-plugin/marketplace.json; commit both
bth plugin repo-bundle --check    # CI: exit 1 if either is missing or stale

Users then run /plugin marketplace add maraxen/bathos and /plugin install bathos@bathos. The bundle sits in plugin/, not the repo root, so a root .mcp.json never registers a duplicate project MCP server for people working in the repo. Private repos work for anyone whose git can already clone them (Claude Code uses the machine's own git credentials).

The family index, maraxen/plugins, lists every tool from one catalog. Each entry is a git-subdir source pinned to that tool's release tag; bth plugin index-entry --repo maraxen/bathos prints it:

{"name": "bathos", "source": {"source": "git-subdir", "url": "maraxen/bathos", "path": "plugin", "ref": "v0.13.0a6"}}

A plugin installed from GitHub gets the tool's files, not its Python package, so give the MCP server a launcher that fetches the matching release:

[plugin.mcp]
command  = ["bth-mcp"]
launch   = "uvx"                        # default "path": run `command` as-is
uvx_from = "bathos[mcp]=={version}"     # default "<plugin>=={version}"; or
                                        # "git+https://github.com/maraxen/<repo>@v{version}"

{version} is filled in from the release version the bundle is built from, so the server is always the same release as the plugin (and as the index entry's ref). Install the plugin from one marketplace per machine, either cisternal-local or GitHub: the same plugin from both appears twice.

Where the shared marketplace lives

It is derived data, so its location is configuration, never a baked-in path. The first match wins (<tool> plugin info shows which):

  1. --marketplace PATH
  2. $CISTERNAL_PLUGIN_MARKETPLACE (none/empty disables)
  3. [tool.cisternal] plugin_marketplace = "PATH" in the nearest pyproject.toml
  4. plugin_marketplace = "PATH" in ${XDG_CONFIG_HOME:-~/.config}/cisternal/config.toml

If none of these is set, the first command that needs the marketplace (plugin install|update, assets publish-shared, assets update-all) generates the per-machine config and reports it on stderr:

# ~/.config/cisternal/config.toml
plugin_marketplace = "~/.cisternal/claude-plugin-marketplace"

That file then decides, and you can change it. An existing config file keeps its contents: the key is added as its first line. A malformed config file is an error and is never overwritten. plugin info and --dry-run only report that the file would be written. Set CISTERNAL_PLUGIN_MARKETPLACE=none to opt out; commands then fail instead of generating anything.

Surfaces

Export targets: Claude Code, Cursor, GitHub Copilot, Antigravity, OpenCode, Pi, JCode. Install (plugin install|update) is implemented for Claude Code. For the other surfaces, plugin install <surface> points at plugin export. Codex is not supported yet: there is no emitter, and Codex's documented CLI can add a marketplace (codex plugin marketplace add) but installs only through its /plugins UI. The installer table (INSTALLABLE_SURFACES in cisternal.plugin.app) is where it would plug in.

The CLI is fastmcp-free by design — cisternal.cli imports and runs even in environments without fastmcp installed; asset-export logic never depends on the telemetry/registration surface.

Design notes

  • Telemetry emission never raises: if the pipeline isn't initialized, emit_event/span are no-ops.
  • Registry state is process-scoped — call cisternal.clear_registry() between tests to avoid cross-test contamination.
  • The M2 wire-time MCP callable is a pure passthrough: telemetry and shape adaptation are exclusively owned by the telemetry middleware, never by the registration wrapper itself.

License

MIT — see LICENSE.

Metadata

Release files for cisternal 0.1.1a14

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cisternal 0.1.1a14
File Size Uploaded
cisternal-0.1.1a14.tar.gz 357.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cisternal 0.1.1a14
File Interpreter ABI Platform
cisternal-0.1.1a14-py3-none-any.whl Python 3 none any Details

Total release size: 609.2 kB

Release files / cisternal-0.1.1a14.tar.gz

Download URL cisternal-0.1.1a14.tar.gz
Size 357.5 kB
Tags Source
SHA-256 checksum
How to use checksums
5b00eff4698d943b12f7cbaf71f3b67156339d0bb0249b4d28d56854d5d9fd73
BLAKE2b-256 checksum
How to use checksums
943c350cc06fed94bdd8a1052cfebf3f61fa6692259ccafd77456bb702a3175c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / cisternal-0.1.1a14-py3-none-any.whl

Download URL cisternal-0.1.1a14-py3-none-any.whl
Size 251.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
731d5a04c2c98cf03ff5af7edb2d2297002534c06e4d77fefb3eca97380abd2c
BLAKE2b-256 checksum
How to use checksums
eb815b8a55617837bad2799e604da980011ffa08f37cb6d5eedc5b7c70951cf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page