A governed, provider-agnostic agent SDK — cost budgets, tamper-evident audit, PII redaction, context governance, and record/replay testing as the foundation, not plugins. The second door to the Cendor stack.
Project description
cendor-sdk
A governed agent in 10 lines — cost budgets, tamper-evident audit, and PII redaction built in.
provider-agnostic · local-first · offline by default · sync and async
The only agent SDK where cost budgets, tamper-evident audit, PII redaction, context governance, and record/replay testing are the foundation, not plugins.
Two doors into Cendor
Cendor is production plumbing for LLM applications. There are two ways in — pick by your situation, not by primacy:
| When | What you get | |
|---|---|---|
| 🥇 The libraries (primary) | You already have a framework (LangChain, LlamaIndex, the provider SDKs). | Drop cost, governance, and testing beneath the framework you already use. pip install cendor. |
🚪 cendor-sdk (this repo) |
Starting fresh / want it simple. | A batteries-included, governed agent SDK — you don't need to pick a framework or wire the libraries. pip install cendor-sdk. |
Both doors expose the same primitives — budget, guard, Policy, AuditLog, trace. A team
that starts on the SDK and later adopts a framework drops to the libraries underneath with no
concept rewrite. Movement between doors is continuous, not a migration.
cendor-sdk owns the agent loop, so every governance concern that is best-effort beneath a
framework becomes first-class here: usage is never lost, budgets enforce before the model call,
PII is redacted before send, and the whole run correlates under one trace_id.
Install
pip install "cendor-sdk[openai,anthropic]" # provider SDKs are optional extras
pip install "cendor-sdk[all]" # every provider + interop, batteries included
The install bundles the whole Cendor stack (cendor-core, tokenguard, acttrace, contextkit,
squeeze, cassette) by dependency — you install once and import only from cendor.sdk. Provider
SDKs stay optional extras: [openai], [anthropic], [google], [bedrock], [ollama],
[huggingface], [azure], [foundry-local], plus [mcp] and [otel].
The killer example — a governed agent in 10 lines
from cendor.sdk import Agent, tool, run, budget, guard, Policy, AuditLog
@tool
def get_weather(city: str) -> str:
"""Current weather for a city."""
return f"Sunny in {city}"
agent = Agent(name="assistant", model="gpt-4o", tools=[get_weather],
instructions="Answer using tools when helpful.")
log = AuditLog(system="support", risk_tier="limited", path="audit.jsonl")
with budget(usd=0.25, on_exceed="block"), guard(Policy.default(), audit=log):
result = run(agent, "What's the weather in Paris?", audit=log)
print(result.output) # -> "It's sunny in Paris."
print(result.cost, result.usage) # priced in Decimal, budgeted
print([s.name for s in result.tool_steps]) # -> ["get_weather"]
# audit.jsonl: audit_open -> decision -> llm_call -> tool_call -> llm_call, hash-chained &
# verify()-able, all correlated by one trace_id. Wrap in cassette.using("run.json") to replay it.
Ungoverned still works — on cendor-core alone. Every governance layer is optional and
removable; drop the with block and run(agent, ...) runs bare:
from cendor.sdk import Agent, run
result = run(Agent(name="a", model="gpt-4o", instructions="Be brief."), "Hi")
result = await run.aio(agent, "Hi") # same call, async
Why it's different
| Provider lock | Cost budgets | Tamper-evident audit | PII redaction | Record/replay tests | Local-first | |
|---|---|---|---|---|---|---|
| OpenAI Agents SDK | OpenAI-centric | ✗ | ✗ | ✗ | ✗ | lib |
| LangGraph | agnostic | DIY | DIY | DIY | DIY | lib |
| Anthropic Agent SDK | Anthropic-centric | ✗ | ✗ | ✗ | ✗ | lib |
| CrewAI / Pydantic AI / ADK | varies | ✗/DIY | ✗ | ✗ | ✗ | lib |
| cendor-sdk | agnostic | built-in | built-in | built-in | built-in | yes |
Governance is composed through Cendor's existing bus / interceptor / Sink / Compressor
seams, correlated by trace() — zero SDK-specific glue. Budgets, audit, redaction, and
record/replay all ride the agent loop through those seams, so removing any one is just not entering
its context.
How one turn executes
Runner, for each agent turn:
- Assemble context to the model's budget via
contextkit(optional; falls back to raw messages) — emits anAssemblyReportthat the audit chain records. - Format messages + tool schemas for the target provider.
- Call the model through a
cendor-core-instrumented client insidetrace(run_id)→ emits anLLMCall(usage/cost/reasoning). Pre-call, budget/guard interceptors fire (block / clamp / downgrade, redact-before-send). - Normalize the response → assistant content + tool calls + finish reason.
- Execute any tool calls (each emits a
ToolCall, sametrace_id); append results; loop. - Else finalize → structured-output parse →
Result.
Multi-agent, one correlated tree
Handoff, supervisor/router, and sequential/parallel pipelines — with the correlation that was
impossible beneath frameworks. A whole multi-agent run is one governed, trace_id-correlated
tree, on one verifiable audit chain. Handoff even works across providers:
from cendor.sdk import Agent, run
writer = Agent(name="writer", model="claude-opus-4-8", instructions="Write the brief.")
planner = Agent(name="planner", model="gpt-4o", instructions="Plan, then hand off.",
handoffs=["writer"])
result = run([planner, writer], "Research X and write a brief") # OpenAI ➝ Anthropic handoff
print(result.agents) # ["planner", "writer"]
See docs/multi-agent.md.
Every major provider — one canonical loop
The provider is inferred from the model id (override with provider=). History is held in one
canonical shape, so a run can hand off between providers without rewriting it.
| Provider | Models | Extra |
|---|---|---|
| OpenAI | Chat Completions + Responses API | [openai] |
| Anthropic | Messages API | [anthropic] |
| Google Gemini | google-genai |
[google] |
| AWS Bedrock | Converse API | [bedrock] |
| Ollama | local models | [ollama] |
| Hugging Face | Inference / endpoints | [huggingface] |
| Azure AI Foundry | deployments via the OpenAI v1 endpoint (Chat + Responses) | [azure] |
| Foundry Local | on-device, OpenAI-compatible | [foundry-local] |
More in the box
Everything a real agent needs — all governed through the same seams:
- Streaming —
run.stream/run.astreamyield text deltas + tool events (native for the OpenAI family + Ollama). - Structured output — a dataclass / Pydantic / JSON-schema
output_typeuses each provider's native schema mode. - Reasoning & control —
Agent.extrapassestool_choice,reasoning_effort,top_p,stop, …; o-seriestemperatureis handled for you. - RAG —
VectorIndex+Agent(retriever=…)inject governed retrieval, or expose your store as a@tool. - Memory —
Session(conversation),SummarizingSession(rolling summary),SQLiteSessionStore(durable),context_budget(fit the window). - Embeddings —
embed()/aembed()capture RAG calls on the same cost/audit tree. - Cost governance for any model —
register_model_price(...)so budgets bind on custom / deployment-named ids.
Status — v1.0.0 (stable)
All four phases are shipped, tested offline, and documented; provider coverage and agent capabilities have since been rounded out (streaming, RAG, memory, Hugging Face / Azure Foundry / Foundry Local).
| Phase | Scope | State |
|---|---|---|
| 1 | Governed single agent (loop, providers, tools, structured output, session, governance) | ✅ shipped |
| 2 | Multi-agent orchestration (handoff, supervisor, sequential/parallel) | ✅ shipped |
| 3 | Ecosystem & interop (MCP, A2A, Foundry, OTel span tree, HITL) | ✅ shipped |
| 4 | Production hardening (retries, checkpoints, durable memory) & governed eval | ✅ shipped |
Docs
- docs/index.md — start here
- docs/sdk.md — the SDK quickstart & reference
- docs/multi-agent.md — handoff, supervisor, sequential/parallel
- docs/interop.md — MCP, A2A, Foundry/Copilot, OTel, human-in-the-loop
- docs/hardening.md — retries, checkpointed/resumable runs, durable memory
- docs/eval.md — cassette-backed governed eval & regression testing
- CHANGELOG.md
- examples/ — runnable, network-free examples
- plan/CENDOR_SDK_PLAN.md — the full design
License
Apache-2.0.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cendor_sdk-1.0.0.tar.gz.
File metadata
- Download URL: cendor_sdk-1.0.0.tar.gz
- Upload date:
- Size: 99.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ffaade6f36127bf00b615185be53eebe5a3dfbf2246fc2509ee27ccb4717bf6
|
|
| MD5 |
9061e97276d00a9fb6c706de4505f340
|
|
| BLAKE2b-256 |
ea8c659bd4cca7c2a7d9947d45b4e04430c6d7fc4e84511167a406797ce3c443
|
Provenance
The following attestation bundles were made for cendor_sdk-1.0.0.tar.gz:
Publisher:
release.yml on cendorhq/cendor-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cendor_sdk-1.0.0.tar.gz -
Subject digest:
9ffaade6f36127bf00b615185be53eebe5a3dfbf2246fc2509ee27ccb4717bf6 - Sigstore transparency entry: 2080844122
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-sdk@bafe99d4fcf3e04f03374131dd7a861f0dde80e3 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/cendorhq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bafe99d4fcf3e04f03374131dd7a861f0dde80e3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cendor_sdk-1.0.0-py3-none-any.whl.
File metadata
- Download URL: cendor_sdk-1.0.0-py3-none-any.whl
- Upload date:
- Size: 62.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4efdebcba5c2bc3c619250b22cb183f2d4e52c4b7de6de34841fbe631b95bbbf
|
|
| MD5 |
011a84bbb8c6a7d4a91198d206f42709
|
|
| BLAKE2b-256 |
fa4985e1fbcc7492aa6838d65ad755ded09ea322160c5431d8caee93ac949e8f
|
Provenance
The following attestation bundles were made for cendor_sdk-1.0.0-py3-none-any.whl:
Publisher:
release.yml on cendorhq/cendor-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cendor_sdk-1.0.0-py3-none-any.whl -
Subject digest:
4efdebcba5c2bc3c619250b22cb183f2d4e52c4b7de6de34841fbe631b95bbbf - Sigstore transparency entry: 2080844448
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-sdk@bafe99d4fcf3e04f03374131dd7a861f0dde80e3 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/cendorhq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bafe99d4fcf3e04f03374131dd7a861f0dde80e3 -
Trigger Event:
push
-
Statement type: