Try it now
uvx --from ctxttools cdir
Then install it
uv tool install ctxttools
ctools is a suite of simple tools for navigating through the sessions of the most popular LLM agents and coding tools. It allows you to search your history, transfer conversations, filter them, and even share them among machines and users.
It supports Claude Desktop, Claude Code, Codex, Pi, Hermes, Goose, Kilo, FreeBuff, Cline, omp, and OpenClaw.
Here's an example:
I look for mentions of snake game in opencode using cgrep
$ cgrep "snake game" opencode opencode/ses_a1d1a5a4df984ee698cab4a7:1:user: make a simple snake game in python opencode/ses_a1d1a5a4df984ee698cab4a7:2:assistant: I'll make a terminal-based snake game using `curses` (standard library, no dependencies). opencode/ses_a1d1a5a4df984ee698cab4a7:3:assistant: Done — `snake.py` is a terminal snake game using Python's built-in `curses` module (no dependencies).
cool let's look at the status of that session using cdir
$ cdir -l opencode/ses_a1d1a5a4df984ee698cab4a7 ID NAME MODIFIED SIZE MSGS PATH ses_a1d1a5a4df984ee698cab4a7 make a simple snake game in python 2026-10-07 16:56 2.7 KB 3 /home/chris/.local/share/opencode 1 session(s)
Alright let's copy that session from opencode into pi!
$ ccopy opencode/ses_a1d1a5a4df984ee698cab4a7 pi Copied 3 message(s) from opencode/ses_a1d1a5a4df984ee698cab4a7 to a new pi session: e357f435-f813-463a-b313-f97f1051f3cb Resume it with: pi --session e357f435-f813-463a-b313-f97f1051f3cb
As you can see, these are unix-friendly tools using simple paradigms that you are already familiar with.
Tools
| Tool | What it does | Docs |
|---|---|---|
cgrep |
Search conversation content (grep across every agent) | docs/cgrep.md |
cdir |
List sessions (ls for your history) | docs/cdir.md |
ccat |
Print one or more conversations (cat for your history) | docs/ccat.md |
ccopy |
Copy a whole conversation from one agent to a new session in another | docs/ccopy.md |
crecover |
List, verify, and restore the storage backups ccopy takes before each write | docs/crecover.md |
cextract |
Extract concepts from a session into files, and inject them back | docs/cextract.md |
cconnect |
Live concept pipelines between sessions | docs/cconnect.md |
cdu |
Token usage per session (du for context windows) | docs/cdu.md |
crm |
Remove concepts from a session (mdel) | docs/crm.md |
ctools-web |
Browser dashboard for all of the above | Web dashboard |
Every tool takes --version. The session-mover tools (ccopy, cextract, cconnect, crm) also take --verbose for structured logging. Each tool's full flag reference, output formats, and examples live in its docs/ page — the table above is just the 30-second version.
The problem
You talk to LLMs all day. Over weeks, you build up a set of constraints, preferences, and goals. These live in your conversations as system messages. They are valuable. They are also trapped.
Say you have been working with opencode for a month. You have refined your coding style through dozens of sessions. Now you start a new Claude Code project and you want those same preferences. You could copy them by hand. Or you could cextract them out and cextract them back in, or copy the whole conversation across with ccopy. Either way, your memory travels with you.
How it works
ctools is a substrate for moving memory between context windows. Cross-platform, agent-agnostic, designed like a bus.
Two stages: extraction and filtering.
- A strategy extracts concepts from a conversation. It defines what counts as a concept and how to find it: regex patterns, LLM extraction, whatever. Different strategies produce different ontologies from the same conversation, because context is contestable.
- A filter selects which extracted concepts reach a destination. It's a binary classifier: pass through or filter out. One concept list can fan out to many destinations, each with its own filter. A coding agent gets coding preferences, a security agent gets security constraints, a PM agent gets goals, all from the same source.
A filter can be as simple as a type or substring match, or as smart as a decision model — a small local classifier that scores each concept and lets it through only when confident. Point a filter at a model and you get semantic filtering with no free text to parse: ask "is this concept about security?" and gate on the answer. The model is asked either through Ollama's typed /v1/systemone endpoint (calibrated probabilities, the home of decision-dedicated models) or through any OpenAI-compatible /v1/chat/completions server (portable, lettered decision-task prompt) — one endpoint field decides. See cextract for the decision-model and JSON-RPC filter configs.
graph LR
SRC["source session"] --> STRAT["strategy<br/>(extraction)"]
STRAT --> CONCEPTS["concept list"]
CONCEPTS --> FA["filter A"]
CONCEPTS --> FB["filter B"]
CONCEPTS --> FC["filter C"]
FA --> DEST_A["destination A"]
FB --> DEST_B["destination B"]
FC --> DEST_C["destination C"]
Context windows are endpoints: opencode, Claude Code, Codex, Pi. They all speak different protocols, but they all consume the same packets. See cextract for the concept packet format, strategies, and the filter configs (decision model and JSON-RPC).
Write safety
The session-mover tools write into your agents' live storage — for the SQLite-backed agents (opencode, goose, kilo, hermes) that's the database that holds every session of that agent. A write that fails or is interrupted part-way can corrupt it, and losing an agent's whole history would be unforgivable.
So every local write is guarded:
- ctools snapshots the agent's storage first (SQLite via
VACUUM INTO— a consistent copy even mid-write; file agents via a tree copy), kept under~/.local/share/ctools/backups/<agent>/(20 newest per agent). - After the write it verifies the storage: the DB opens,
PRAGMA integrity_checkpasses, the session table still exists, and the session count did not decrease. A database that is structurally valid but emptied of data is caught too. - If anything looks wrong, the snapshot is restored automatically — you get a clean error, not a broken database.
And because a restore is itself a write, it snapshots what it overwrites, so
every restore is undoable. For the out-of-band case (you notice days later
that an agent's history vanished), crecover list / verify / restore are
there. Details: ccopy — safety and
crecover.
Supported endpoints
| Agent | Storage |
|---|---|
| claude | JSON |
| claude-code | JSONL |
| opencode | SQLite |
| kilo | SQLite (opencode fork, same schema) |
| codex | JSONL |
| pi | JSONL |
| goose | SQLite |
| hermes | SQLite (~/.hermes/state.db, $HERMES_HOME) |
| cline | JSON (~/.cline/data/tasks/) |
| omp | JSONL (~/.omp/agent/sessions/, tree journal) |
| freebuff | JSON (~/.config/<codebuff>/projects/) |
| openclaw | SQLite (per-agent, ~/.openclaw/agents/, OPENCLAW_STATE_DIR) |
OpenClaw is a personal assistant rather than a terminal coding agent, so it runs in a local Gateway that owns its store; ctools reads and exports its sessions but does not seed new ones (the store has canonical-index and integrity checks that a raw insert cannot satisfy).
Run cdir to see which endpoints are found on your system and where they store data.
Every endpoint is one class in ctools/agents.py. The base Agent defines the whole interface the tools use — sessions(), messages(), raw_messages(), lines(), inject_system(), inject_toolcall(), remove_messages(), create_session() — and the storage-shaped subclasses (JsonAgent, JsonlAgent, SqliteAgent) implement most of it. Adding an endpoint means writing one subclass and adding it to AGENT_CLASSES; no command changes.
from ctools.agents import JsonlAgent, Session
class MyAgent(JsonlAgent):
name = 'myagent'
description = 'My Coding Agent'
session_pattern = 'sessions/**/*.jsonl'
@classmethod
def default_base_path(cls):
return Path.home() / '.myagent'
def sessions(self):
...
Anything the storage cannot do — pi's session trees cannot be rewritten in place — raises UnsupportedOperation instead of being silently skipped.
MCP server
There is an MCP server for use from pi, Claude, opencode, Cursor, or anything else that speaks MCP.
Tools: list_agents, list_sessions, search_sessions, export_session, extract_concepts, copy_concepts, get_session_concepts.
Quick install (pi)
One command does the whole job — install into an isolated venv (if needed) and register with pi:
pipx run 'ctxttools[mcp]' ctools-mcp install
# or, with uv:
uv tool run 'ctxttools[mcp]' ctools-mcp install
It detects an existing uv tool or pipx install of ctxttools and uses it instead. Verify with:
pi mcp list # expect: ctools: connected, 7 tools
Then use it inside pi — run /mcp to inspect, or just ask the agent (the tools are also reachable through codemode / tool search).
Uninstall with ctools-mcp uninstall (removes the pi entry; pass --all to also remove Claude Code and opencode entries).
Other clients
Run ctools-mcp print-config to get the JSON snippet for any MCP host, e.g. for opencode (opencode.json under "mcp") or Claude (.mcp.json):
{
"mcpServers": {
"ctools": {
"command": "python",
"args": ["-m", "ctools.ctools_mcp"]
}
}
}
(command should point at the interpreter of a venv that has ctxttools[mcp] installed — ctools-mcp install prints the exact one for your machine.)
Web dashboard
Prefer a mouse to a man page? ctools ships a browser dashboard — a small local web server (Python stdlib only, no web dependencies) with the same library underneath. Same on macOS, Windows, and Linux:
$ ctools-web # installed command
# or, straight from a checkout, no install needed:
$ python3 ctools/ctools-web.py # or: python -m ctools.webui
ctools 0.2.1 dashboard: http://127.0.0.1:8765
Open the URL. The navbar has Overview, Sessions, and Concepts, plus a search box (press /) and a power button to stop the server. Clicking a session anywhere opens its Conversation view:
- Overview — every supported agent, installed or not, with session counts and total tokens, and a jump into each agent's sessions.
- Search —
cgrepin the browser: one box, searches session ids, names, paths and the content of every conversation at once; click a match to open that conversation. - Sessions —
cdirfor one agent (or all of them): newest, largest, or by token usage, filterable. - Conversation —
ccatwith the session's messages, token usage, the resume command, concept extraction (cextract), a markdown export, and Copy —ccopyto any other installed agent, which creates a fresh session there (source untouched) and tells you how to resume it.
The server binds to 127.0.0.1 by default, so nothing is exposed to your
network unless you pass --host 0.0.0.0. There is one write operation:
Copy. Everything else is read-only against the agents' storage.
Stop it with Ctrl-C, or with the power button (⏻) in the top-right of the page — it asks for confirmation, then shuts the server down cleanly.
ctools-web --port 9000
Installation
pip install ctxttools
For MCP server support:
pip install ctxttools[mcp]
Library
Works as a Python library too.
from ctools.agents import REGISTRY, OpencodeAgent, SessionNotFound
from ctools.lib import get_formatter
from ctools.cgrep import grep_session
from ctools.cextract import extract_concepts_from_messages
from ctools.cdu import count_tokens, get_session_tokens
from ctools.filterlib import JSONRPCFilter, SystemOneFilter, load_filter
from ctools.log import configure_logging, get_logger
Everything goes through an agent:
from ctools.agents import REGISTRY
agent = REGISTRY['opencode']
for session in agent.sessions():
print(session.id, session.name, session.size)
for message in agent.messages('ses_abc123'):
print(message.role, message.content)
Point an agent somewhere else by constructing it with a path:
from ctools.agents import OpencodeAgent
agent = OpencodeAgent('/backup/opencode')
Where to go next
- Per-tool reference — the Tools table above links to a
docs/page for each command: every flag, output format, and a worked example. - The interchange format — how a conversation becomes a portable, lossless envelope and back, and why 2N agents compose into N² transfers. See interchange.
- Filtering with decision models — the full decision-model and JSON-RPC filter configs, with live examples. See cextract and cconnect.
- Adding an endpoint — one
Agentsubclass plus a line inAGENT_CLASSES. See Supported endpoints.
Credits
ctools was extracted from Gab n' Go. It's named after GNU mtools, which does the same thing for DOS floppies — because your context window is about the size of a DOS-floppy. The flag habits are GNU's too: --version, -1, -S, -u, and the rest behave the way you'd expect.
Metadata
Release files for ctxttools 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ctxttools-0.3.1.tar.gz | 134.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ctxttools-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 275.1 kB
Release files / ctxttools-0.3.1.tar.gz
| Download URL | ctxttools-0.3.1.tar.gz |
|---|---|
| Size | 134.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7590dd906c25dfff07bd0a50d3bd110f347a96a931092e08d859580b62caed68
|
|
BLAKE2b-256 checksum How to use checksums |
0b7b0de144e2e4fb7b0bccdb17e2d5c317b640b6c0cc2e322115154d0b10c838
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.15
|
Release files / ctxttools-0.3.1-py3-none-any.whl
| Download URL | ctxttools-0.3.1-py3-none-any.whl |
|---|---|
| Size | 140.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3dcb565599bbea92b06f4ee93bf07cb38aac5dc9aa4dd92b27ecadb7b448a520
|
|
BLAKE2b-256 checksum How to use checksums |
bf5945507dd39a503ac9b7b18a101cc73da078a400510b133b5ab5d484b32e0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.15
|
Security for ctxttools 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.