Architecture — the libraries inside the SDK
cendor-sdk is two layers, one install: an agent loop on top, and the seven Cendor libraries
underneath. The loop gives you Agent, tool, run, sessions, RAG, multi-agent handoff, and
interop; every governance behaviour is a library beneath it — the SDK contains no governance
logic of its own, it just wires the libraries to the loop through
cendor-core’s event bus. This page is the map: where each library shows up in the
SDK, and which SDK surface it powers.
The two layers
The SDK adds the orchestration — the ReAct loop, tool dispatch, handoff, memory, retrieval — and
delegates every governance concern (context, cost, safety, evidence, testing) downward. So the
count that matters is seven libraries: the six tools plus the cendor-core foundation they all
stand on.
Two libraries act on both sides of a call — guardrails gates the input and the output,
acttrace guards (redacts) before send and audits after. Everything ships under the cendor.*
(Python) / @cendor/* (TypeScript) namespace; the SDK re-exports the objects you reach for, so
from cendor.sdk import budget is the same budget as from cendor.tokenguard import budget —
drop down to the libraries at any time, it’s continuous, never a migration.
Where each library is used in the SDK
One section per library. Each carries a “Where it’s used in the SDK” table — the SDK symbol or parameter, what the library does there, and the SDK concept page that documents it — plus the absolute link to the library’s own docs.
contextkit — fit history to the window ●
Every turn, the loop assembles the running conversation (and any retrieved passages) into the
model’s token budget. Set Agent(context_budget=…) / contextBudget and each turn packs history
through contextkit, emitting an audited AssemblyReport — a receipt of what was kept, shrunk, or
dropped.
| SDK surface | What contextkit does there | Concept page |
|---|---|---|
Agent(context_budget=…) / contextBudget | assemble history to a token budget, once per turn | Memory & sessions |
| the runner’s per-turn assembly | packs Session history + retriever passages into the window | Agents & the loop |
| RAG passage injection | retrieved context enters assembly like any other block | Retrieval (RAG) |
Full library docs: /docs/contextkit.
squeeze — compress oversized blocks ●
squeeze has no direct SDK API — and that’s deliberate. It is a hard dependency, but you never
call it from cendor.sdk. At import it registers a Compressor on
cendor-core’s protocol seam; contextkit picks it up whenever a block is marked
evict="compress", and session trimming benefits the same way. Install it and it engages;
without it present, evict="compress" falls back to truncation.
| SDK surface | What squeeze does there | Concept page |
|---|---|---|
| no direct symbol | auto-wires as a Compressor on core’s seam at import | (this section) |
Agent(context_budget=…) + evict="compress" blocks | contextkit calls squeeze to shrink oversized blocks reversibly | Memory & sessions |
| session trimming | the same compression path keeps long memory bounded | Memory & sessions |
Full library docs: /docs/squeeze.
tokenguard — cap and attribute spend ●
Every governed run prices its calls and can refuse an over-budget one before it fires. The budget,
attribution, and price-table objects are tokenguard’s, re-exported from cendor.sdk.
| SDK surface | What tokenguard does there | Concept page |
|---|---|---|
budget(...) / withBudget(...), BudgetExceeded | pre-flight (or post-flight) spend cap around a run | Governance → budgets |
track(...) / report(...) | attribute ambient spend by feature / user / agent | Governance → attribution |
Agent(max_usd=…) / maxUsd | per-agent pre-flight cap (block semantics), enforced per segment | Multi-agent → per-agent budgets |
register_model_price(...) / configure(...) | price unpriced / custom-deployment models so USD caps bind | Providers → pricing |
Full library docs: /docs/tokenguard.
guardrails — the four-stage gate ●
Agent(guardrails=[…]) attaches the deterministic cendor-guardrails Gate to
four points of the loop — input, tool call, tool result, output — with block / redact / flag.
The rule factories, the LLM-judge helpers, the presets, and the policy loader all come from the
library.
| SDK surface | What guardrails does there | Concept page |
|---|---|---|
Agent(guardrails=[…], guardrail_mode=…) | gate four stages; blocking or parallel execution | Guardrails |
rules.* (keyword_deny, regex_rule, intent, custom_category, …) | the deterministic + semantic rule factories | Guardrails → core concepts |
judge / task_adherence | build an LLM-judge check (intent / tool-call alignment) | Guardrails → task adherence |
presets, policy_schema, load_policy | curated starters + versioned policy files | Guardrails → hosted rails & config |
Full library docs: /docs/guardrails.
cassette — record once, replay forever ●
Testability is cassette. The eval harness drives it, and you scope a recording with cassette
directly — but cassette is not re-exported by the SDK. Import it from the umbrella:
from cendor import cassette (Python) / @cendor/cassette (TypeScript). It surfaces in the SDK
only through the eval harness (evaluate / EvalCase).
| SDK surface | What cassette does there | Concept page |
|---|---|---|
from cendor import cassette (not cendor.sdk) | record/replay a whole run, deterministic + offline | Governance → testing |
the eval harness (evaluate, EvalCase) | replays recorded trajectories as CI tests — behaviour and spend | Eval & regression testing |
Full library docs: /docs/cassette.
acttrace — redact + tamper-evident audit ●
Privacy and evidence are acttrace. guard(Policy…) redacts PII before the provider sees it; an
AuditLog records every step in a hash chain you can verify() offline; and the SDK bridges
acttrace’s detector catalogue into the Gate as rules.pii / secrets / entropy.
| SDK surface | What acttrace does there | Concept page |
|---|---|---|
guard(Policy…), Policy, PolicyViolation | process-global PII/secret redaction on the interceptor seam | Governance → audit + redaction |
AuditLog, verify(...) | append every event to a tamper-evident chain; verify offline | Governance → audit + redaction |
rules.pii / rules.secrets / rules.entropy | acttrace detectors bridged into the Gate, gating all four stages (incl. tool_output) | Guardrails → PII & secrets |
Full library docs: /docs/acttrace. Evidence to support compliance — never a compliance guarantee.
core — the seam every call rides ●
cendor-core is the foundation: the instrument() seam, the in-process event bus, the price
table, and the trace correlation the whole loop rides. The SDK’s only hard dependency is core;
every other library cooperates through it.
| SDK surface | What core does there | Concept page |
|---|---|---|
the loop’s model calls (instrument) | wrap the provider client once; publish a normalized LLMCall | Agents & the loop → how it works |
trace(...) / current_trace_id | correlate every call & tool in a run under one id | Agents & the loop |
prices / provider detection | count tokens, price usage, detect the provider from the model id | Providers |
Full library docs: /docs/core.
The feature → library map
The canonical map of which library powers which SDK surface. (The Home and Getting started pages carry short versions; this is the full one.)
| I want to… | SDK surface | Library underneath |
|---|---|---|
Understand Agent / tool / run / Result | the loop | cendor-core |
| Fit history to a token budget | Agent(context_budget=…) | contextkit (+ squeeze when installed) |
| Compress oversized blocks | auto via evict="compress" | squeeze — no direct SDK symbol |
| Cap spend / attribute cost | budget / track / report, Agent(max_usd=…) | tokenguard |
| Block / redact / flag at four stages | Agent(guardrails=[…]), rules.* | cendor-guardrails |
| Redact PII, keep tamper-evident evidence | guard(Policy…), AuditLog, verify | acttrace |
| Record once / replay; gate regressions in CI | from cendor import cassette, evaluate / EvalCase | cassette |
| Remember across turns / processes | Session, SQLiteSessionStore, context_budget | contextkit |
| Give the agent my documents | Agent(retriever=…), VectorIndex, embed | contextkit + cendor-core |
| Use more than one agent | handoff / supervisor / sequential / parallel | — (SDK orchestration) |
| Connect Gemini / Bedrock / Ollama / HF / Azure | Agent(provider=…) | cendor-core |
| Survive crashes, retries, long runs | RetryPolicy, checkpoint= | — (SDK) |
The bottom two rows are the SDK’s own layer — orchestration and hardening — the only behaviours not delegated to a library.
How it works
One governed turn, top to bottom — governance fires at core’s seam, not inside your code, and every touchpoint is a library on the bus:
%%{init: {"flowchart": {"htmlLabels": false}} }%%
graph TD
IN["run(agent, input)"]
ASM["assemble context<br/>(contextkit; squeeze compresses oversized blocks)"]
PRE["pre-flight<br/>(tokenguard budget · guardrails input gate · acttrace guard redact)"]
CALL["the model call<br/>core.instrument() → the bus, inside trace(run_id)"]
TOOLS{"tool calls<br/>requested?"}
EXEC["execute tools<br/>(guardrails gate tool_call + tool_output)"]
OUTG["output gate<br/>(guardrails)"]
AUD["record + audit<br/>(cassette records · acttrace chains the evidence)"]
DONE["Result<br/>output · steps · usage · cost"]
IN --> ASM --> PRE --> CALL --> TOOLS
TOOLS -->|yes| EXEC --> ASM
TOOLS -->|"no (or max_turns)"| OUTG --> AUD --> DONE
classDef seam fill:#2563EB,color:#ffffff,stroke:#1E40AF;
classDef stop fill:#F43F5E,color:#ffffff,stroke:#E11D48;
class CALL seam;
class PRE stop;
Because every model and tool call flows through the same bus under one trace_id, each library
sees the others’ work for free — acttrace’s audit log already knows the context decisions, the
cost, the tool calls, and the guardrail verdicts, without importing contextkit, tokenguard, or
guardrails.
Honest limits
- The SDK owns orchestration, not governance. The loop, handoff, and hardening are the SDK’s; everything else is a library you can also use standalone. If a claim is about cost, safety, or evidence, the library docs — and the parity matrix — are the source of truth.
- squeeze engages only when installed. With no squeeze present,
evict="compress"truncates instead of compressing — no error, just a coarser result. - Evidence, not compliance.
acttraceproduces evidence to support a compliance case; it doesn’t make one, and the SDK inherits that limit unchanged. - Parity is documented, not version-coupled. Where the TypeScript SDK trails Python (e.g.
reask_on_output_trip,stream_check_window), the pages say so and the parity matrix is authoritative.