Languages & parity

Cendor ships in two languages: Python (cendor.*, the reference implementation, on PyPI) and TypeScript/JavaScript (@cendor/*, on npm — ESM-only, Node LTS first, edge runtimes supported). Both are implementations of the same versioned format specs, so the artifacts that matter — cassettes, audit chains, price tables, bus events — are byte-for-byte interoperable, checked by committed conformance vectors in both CIs — each language verifies artifacts written by the other (the TypeScript CI replays Python-written fixtures; the Python CI verifies a JS-written audit chain).

pip install cendor-libs        # the whole stack; `cendor` is an alias
from cendor.core import instrument
client = instrument(OpenAI())
npm i @cendor/libs             # the whole stack (umbrella)
import { instrument } from '@cendor/core';
const client = instrument(new OpenAI());

One API, two spellings

The TypeScript API is derived from the Python public API, mechanically:

RulePythonTypeScript
Namessnake_case (max_turns, on_exceed)camelCase (maxTurns, onExceed)
Scopescontext managers (with budget(...):)async callbacks (withBudget(cfg, fn)) / decorators (budget(cfg)(fn))
MoneyDecimal — never a floatdecimal.js — never a float; value-equal across languages
ErrorsBudgetExceeded, PolicyViolation, …identical names
Defaultsthe samethe same
Tool schemas (SDK)derived from type hints + docstringdeclared with zod

Cross-language guarantees

These are tested by committed conformance vectors, not promised:

  • A cassette recorded in Python replays in TypeScript (and vice-versa) — same request hashes, same wire format.
  • An audit chain written in TypeScript verify()s in Python — identical canonical bytes and HMAC inputs.
  • Prices and token counts match exactly — same bundled snapshot, tiktokenjs-tiktoken parity, decimal-exact math.
  • Bus events (LLMCall / ToolCall / Usage / Money) share one schema across languages.

Parity matrix — libraries

Legend: ✅ ported · 🚧 partial/scoped · Py-only deliberately not ported.

CapabilityPythonTypeScriptNotes
Money (decimal, never float)Decimaldecimal.js; value-equal across langs
Usage / LLMCall / ToolCallsnake_case ↔ camelCase fields; type names identical
Event bussubscribe/emit/unsubscribe; error isolation
Price table + estimate()same bundled snapshot; refresh() async in TS
Token countingtiktokenjs-tiktoken — exact counts match
instrument() providers✅ 6 (OpenAI, Anthropic, HuggingFace, google-genai, Bedrock, Ollama)✅ 6 (OpenAI, Anthropic, HuggingFace, google-genai, Bedrock, Ollama)Bedrock JS auto-detects a boto-shaped converse(); aws-sdk-v3 rides the SDK provider
instrument() streaming / interceptors
core otel spans / ingest()span() + ingest(); @opentelemetry/api optional peer — span is a no-op without it
LangChain CendorCallbackHandler@cendor/core/langchain; recording-only in both; reads usage_metadata, correlates by root-run traceId
trace() correlation✅ contextvars✅ AsyncLocalStorage
tokenguard budgets / track / report / sinksSQLite / Queue / OTel sinks in both
contextkit assemble / evict / orderTS collapses sync+async into one async assemble()
squeeze compress / decompressdeterministic; handle ids match
cassette record / replaycross-language replay, vector-verified
cassette local_embedding_scorer (bundled model2vec)Py-onlyno JS static-embedding package exists; TS uses the BYO embeddingScorer(embedFn) / openaiEmbeddingScorer seam instead
cassette storagefsfs + memory (+ IndexedDB-shaped)pluggable adapters
acttrace chain / verify / signcross-language verify (HMAC + _meta)
acttrace detectors✅ regex + Presidio NER✅ regex/pattern (20 detectors) + NER🚧 NER via optional compromise (English-only, lighter than Presidio — not parity); nerAvailable() reports presence

Parity matrix — SDK

CapabilityPythonTypeScript
Agent / tool / run / Result✅ (zod tool schemas)
Providers✅ ten paths✅ ten paths (OpenAI, Anthropic, HuggingFace, Azure chat + responses, Foundry Local, Ollama, Gemini, Bedrock) — HF/Ollama/Gemini/Bedrock usage capture rides @cendor/core’s provider detection
Sessions & memory✅ (+ SQLite store)✅ (better-sqlite3 + memory adapters)
Handoff / supervisor / pipelines
Structured output
Streaming✅ (incremental single-agent + multi-agent)
Governance re-exports✅ (the real @cendor/* objects)
Live progress / prompt caching / live OTel spans
MCP client (tools / prompts / resources)✅ (@modelcontextprotocol/sdk optional peer)
Checkpoint / resume✅ (atomic JSON; single + multi-agent)
A2A server / client✅ (JSON-RPC; serve() on node:http)
Foundry / Bot-Framework adapter

Runtime targets (TypeScript)

PackageNodeEdge (Workers)Browser
@cendor/core🚧 types/bus/prices/tokens are pure; instrument wraps fetch SDKs
@cendor/contextkit, @cendor/squeeze✅ pure compute
@cendor/tokenguard⚠️ advisory only — enforcement is server-side
@cendor/cassette✅ (fs)✅ (adapter)⚠️ memory/IndexedDB adapter
@cendor/acttrace❌ never — signing keys can’t live in a client
@cendor/sdk✅ (HTTP/SSE transports; MCP via @modelcontextprotocol/sdk, Node)❌ keys-in-browser anti-pattern

Governance is only real where the user can’t tamper with it. Budgets-as-enforcement, audit-as-evidence, and redaction-as-guarantee are server-side by definition — in every language. Browser builds of the pure-compute libraries are UX aids (live token/cost preview, context assembly in the chat UI), and that’s all they claim to be.

Honest limits

  • Versions are independent across languages. Python and TypeScript release on their own cadence; this page — not matching version numbers — is the parity contract.
  • A couple of surfaces remain Python-only — cassette’s bundled local_embedding_scorer (bring your own embedFn in TS). AWS Bedrock auto-detection matches a boto-shaped converse(); aws-sdk-v3’s send(ConverseCommand) is captured via the SDK provider rather than instrument(). (The LangChain / LangGraph callback handler is now in both languages — TS via @cendor/core/langchain; keyless Entra-ID auth for Azure is in both too — TS via the azureADTokenProvider option.)
  • NER backends differ by language, and it’s not parity. Python uses Microsoft Presidio (spaCy transformer models); TypeScript uses the optional compromise engine (npm install compromise) — synchronous (acttrace’s tamper-evident append is sync, so an async transformer NER can’t plug in), English-only, and with lower recall/precision. Treat the TS NER as an extra layer, not a sole PII control. nerAvailable() reports whether the backend is installed.
  • Docs code samples default to Python where a tab pair isn’t shown; the mapping rules above translate mechanically.