Build an LLM agent with spending limits, a tamper-evident audit trail, PII redaction, and record/replay testing built in from the start — a governed agent in about 10 lines.
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
A thin, provider-agnostic agent SDK where cost budgets, tamper-evident audit, PII redaction, context governance, and record/replay testing are the foundation, not a plugin.
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. It's the simple,
batteries-included door into the Cendor stack — you don't need
to pick a framework or wire the libraries. (Already have a framework? Compose the libraries beneath
it: pip install cendor-libs.)
Install
pip install "cendor-sdk[openai,anthropic]" # provider SDKs are optional extras
pip install "cendor-sdk[all]" # every provider + interop, batteries included
# Using uv? Same names, same extras: `uv add` instead of `pip install`.
The install bundles the whole Cendor stack — all seven libraries (cendor-core, tokenguard,
guardrails, acttrace, contextkit, squeeze, cassette) — by dependency, so 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].
Using an AI coding assistant? uvx cendor-init (Python) / npx @cendor/init (TS) wires it up — or
point it at cendor.ai/docs/for-ai-assistants.
A governed agent in 10 lines
Auth: OPENAI_API_KEY from your environment (or Agent(api_key=…), or a pre-built client=).
The SDK builds the provider client for you — there's no Cendor-specific key. Full table:
docs/providers.
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
run.aiois natively async for OpenAI (Chat + Responses — and the Azure AI Foundry / Foundry Local paths that use the same client), Anthropic, Google Gemini (google-genai'saio.models.generate_content), Ollama, and Hugging Face. Bedrock's boto3converseis blocking, sorun.aiooffloads it to a worker thread (asyncio.to_thread) — the event loop keeps running, and the run's governance scope still attaches.
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.
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"]
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. Incremental token-level deltas on the OpenAI Chat family (including Azure AI Foundry, Foundry Local, and Hugging Face), Anthropic, and Ollama; OpenAI Responses, Gemini, and Bedrock yield the whole response as one delta. - 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. - Interop — MCP tools, A2A server/client, a Foundry/Copilot adapter, and human-in-the-loop approvals on the same audit chain.
- Production hardening — retry policies, and checkpointed/resumable runs so a crashed run continues where it stopped.
- Observability, zero telemetry code — configure any OpenTelemetry provider and
run()emits anagent.runspan tree with usage/cost rollups and governance correlated to the run;CENDOR_TELEMETRY=offswitches it off. Cendor has no endpoint or key — it emits into your backend. - Agent identity —
Agent(id="reg-42")rides the semconvgen_ai.agent.idon every span and governance row, so a budget block says which agent it stopped; no id means the attribute is omitted, never fabricated.
Scope & honest limits
on_exceed="raise"overshoots by one call — it's post-flight. For a true ceiling use"block".- Unpriced models record
$0, so a USD cap can't bind on them —register_model_price(...)or use a token cap. guardredacts what its detectors find — regex/pattern detectors plus Presidio NER (an optional extra). See acttrace for coverage.guard/ interceptors are process-global — they register on the single in-process bus, so install policy once at startup rather than toggling per request.- Evidence, not compliance. The audit chain supports a compliance case; it doesn't make one, and it isn't legal advice.
Docs
Rendered, searchable, with a page-wide Python / TypeScript toggle at cendor.ai/docs/sdk — the same markdown also renders on GitHub.
- Quickstart & reference · Architecture — the libraries inside the SDK
- Agents & the loop · Multi-agent orchestration
- Governance · Guardrails · Observability
- Providers · Interop — MCP, A2A, Foundry, OTel, HITL
- Production hardening · Governed eval · FAQ
- Runnable, network-free examples · Changelog
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
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.21.0.tar.gz.
File metadata
- Download URL: cendor_sdk-1.21.0.tar.gz
- Upload date:
- Size: 413.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d34fa4c22f6d97f976698696335d01737f15e48d5a413c3feffdd7401b14246
|
|
| MD5 |
ef606e8eb2d6a00a5f833c2fa4fe8933
|
|
| BLAKE2b-256 |
944d2c160e6f48df28f0a0e9783fbde63f6e2971273f60b4d3194a982d67ea09
|
Provenance
The following attestation bundles were made for cendor_sdk-1.21.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.21.0.tar.gz -
Subject digest:
8d34fa4c22f6d97f976698696335d01737f15e48d5a413c3feffdd7401b14246 - Sigstore transparency entry: 2299762733
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-sdk@322db5a885f479733e00c419d56cc4c7cc04941b -
Branch / Tag:
refs/tags/v1.21.0 - Owner: https://github.com/cendorhq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@322db5a885f479733e00c419d56cc4c7cc04941b -
Trigger Event:
push
-
Statement type:
File details
Details for the file cendor_sdk-1.21.0-py3-none-any.whl.
File metadata
- Download URL: cendor_sdk-1.21.0-py3-none-any.whl
- Upload date:
- Size: 113.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5323c5a870d510a4df07b21bd0eedb501b9fc0f12950fef7b655dc2545911661
|
|
| MD5 |
2b483e65b2e10395ee30cbc4afa3b3f1
|
|
| BLAKE2b-256 |
04f2ffc64c248d85e65b2d78803426912cd0acc94bc1b128e688a264362d8e2c
|
Provenance
The following attestation bundles were made for cendor_sdk-1.21.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.21.0-py3-none-any.whl -
Subject digest:
5323c5a870d510a4df07b21bd0eedb501b9fc0f12950fef7b655dc2545911661 - Sigstore transparency entry: 2299762837
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-sdk@322db5a885f479733e00c419d56cc4c7cc04941b -
Branch / Tag:
refs/tags/v1.21.0 - Owner: https://github.com/cendorhq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@322db5a885f479733e00c419d56cc4c7cc04941b -
Trigger Event:
push
-
Statement type: