Cendor
Production plumbing for LLM applications.
Composable primitives for context, cost, testing, and governance — the layer beneath your
LLM app. Framework-agnostic · local-first · offline by default · Apache-2.0. Available for
Python (cendor.* on PyPI) and TypeScript/JavaScript (@cendor/* on npm) — see
Languages & parity.
The problem
You shipped an LLM agent. Then production happened:
- 🧠 Prompts overflow the context window — and naive truncation drops exactly the wrong things.
- 💸 Cost is a black box — a looping agent quietly burns money, and you can’t say which feature or user spent it.
- 🧪 You can’t test it — every run hits a paid, non-deterministic API, so there are no fast, repeatable tests.
- 📋 There’s no audit trail — you can’t show what the agent saw, did, cost, or refused to do.
Agent frameworks (LangChain, LlamaIndex, the provider SDKs) decide what your agent does. They don’t handle these cross-cutting, under-the-call concerns. Cendor does — and you keep your framework.
Using LangChain or LangGraph? Cendor plugs into the framework’s own callback system —
CendorCallbackHandler records usage, cost, reasoning, tool calls, and run-correlation with no
client change. Calling a provider SDK directly? One instrument() wrap adds the same recording
plus full enforcement (budgets, redact-before-send, replay). See
Providers → Frameworks.
The fix: wrap your client once, every tool plugs in
from cendor.core import instrument
client = instrument(OpenAI()) # ← the one line you change
import { instrument } from '@cendor/core';
const client = instrument(new OpenAI()); // ← the one line you change
That single wrap publishes every LLM and tool call onto an in-process event bus. Each library subscribes — none patches your client, none imports another — so you add budgeting, recording, or auditing with zero per-call wiring. Read the diagram top to bottom: it’s one request’s lifecycle, with each library labelled where it acts.
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD
YOU["your agent code"]
B["1. Build the prompt"]
PRE["2. Pre-flight<br/>(before the call runs)"]
CALL["3. The LLM call<br/>core.instrument() = the seam"]
POST["4. After the call<br/>(automatic, via the event bus)"]
YOU --> B --> PRE --> CALL --> POST
B --- CK["contextkit<br/>pack context into a budget"]
B --- SQ["squeeze<br/>compress oversized blocks"]
PRE --- TG1["tokenguard<br/>block / downgrade if over budget"]
PRE --- AT1["acttrace<br/>policy guard: flag + block bad input"]
POST --- TG2["tokenguard<br/>record spend by feature / user"]
POST --- CS["cassette<br/>record the run (replay in tests)"]
POST --- AT2["acttrace<br/>append to the tamper-evident log"]
classDef seam fill:#2563EB,color:#ffffff,stroke:#1E40AF;
classDef ck fill:#3B82F6,color:#ffffff,stroke:#2563EB;
classDef sq fill:#22C55E,color:#0F172A,stroke:#16A34A;
classDef tg fill:#8B5CF6,color:#ffffff,stroke:#7C3AED;
classDef cs fill:#14B8A6,color:#ffffff,stroke:#0D9488;
classDef at fill:#F43F5E,color:#ffffff,stroke:#E11D48;
class CALL seam;
class CK ck;
class SQ sq;
class TG1,TG2 tg;
class CS cs;
class AT1,AT2 at;
tokenguard and acttrace each appear twice: they run before the call (cap spend / guard the input) and after it (record cost / append to the log).
The six libraries
Each solves one of those problems, and each works on its own:
| Library | Solves | In one line |
|---|---|---|
| contextkit | prompts overflow | Pack prioritized blocks into a token budget; get a receipt of what was kept / shrunk / dropped. |
| squeeze | a blob is too big | Content-aware, deterministic compression (JSON / logs / code / prose) — fully reversible. |
| tokenguard | runaway cost | Cap spend before a call runs (block / downgrade), and attribute cost per feature / user. |
| cassette | can’t test agents | Record a whole run once (LLM + tool calls), replay it forever — offline, deterministic. |
| acttrace | no audit trail | Tamper-evident, offline-verifiable decision log + policy flags, with compliance evidence packs. |
| core | the shared glue | Types, token counting, offline-first prices, the instrument() seam, and the event bus every tool rides. |
contextkit → squeeze → tokenguard → cassette → acttrace
assemble compress budget test audit
All six are published on PyPI (Python) and as @cendor/* on npm (TypeScript/JS), green
in CI in both languages. Cross-language artifacts interoperate byte-for-byte — a cassette
recorded in Python replays in TypeScript, an audit chain written in TypeScript verifies in
Python. The full feature split is in Languages & parity.
Install
pip install cendor-libs # the whole stack (`cendor` is an alias)
pip install cendor-tokenguard # or any single tool (pulls cendor-core transitively)
Every package imports under the cendor.* namespace.
npm i @cendor/libs # the whole stack (umbrella)
npm i @cendor/tokenguard # or any single tool (pulls @cendor/core transitively)
Every package lives under the @cendor/* npm scope. ESM-only; Node LTS first, edge runtimes
supported.
Libraries or the SDK?
These docs cover the six libraries — the door for teams that already have a loop (LangChain,
LlamaIndex, or direct provider-SDK calls) and want governance beneath it. Cendor’s second
door is cendor-sdk: a governed agent loop (Agent, tool, run) built on
these six libraries, for teams starting fresh. Both doors expose the same primitives — budget,
guard, Policy, AuditLog, trace are the same objects — so you can mix them in one process
and move between them without a migration. Unsure which fits?
FAQ → libraries or SDK.
Prefer to read code? The Cookbook has the full-stack support agent — one
instrument()call, all six tools cooperating — as one copy-paste block.
Where to go next
- Getting Started — install, the one idea (
instrumentonce), and a first budgeted, audited call. - Architecture — the layers, the
instrument()seam, the event bus, and the dependency graph. - Providers & Integration — OpenAI / Anthropic / Bedrock / Gemini / Ollama, managed runtimes via OpenTelemetry, and LangChain / LangGraph via a callback handler.
- Guides & Recipes — copy-paste recipes, including the full-stack support agent.
- Languages & parity — Python ↔ TypeScript: what’s ported, what’s Python-only.
- Benchmarks — reproducible, offline numbers for every package.
- FAQ — common questions.
- The SDK docs — the second door: a governed agent loop built on these libraries.