TypeScript SDK / Python parity
June 7, 2026 · View on GitHub
The TS SDK (@sponsio/sdk) and the Python core share the deterministic
runtime: the formula AST (the LTL syntax tree the engine evaluates), the
evaluator, the grounding layer (the step that turns trace events into
the observable facts the engine reasons over), the pattern library, and
the NL parser. The deterministic semantics on both sides are identical
for the surface that exists in both. The same (formula, trace) pair
always produces the same verdict.
The TS surface, however, is smaller than Python's. Python is the reference implementation; TS lags. This page is the authoritative list of what is and isn't available in TS so users can plan around it.
What's identical
FormulaAST:G,F,U,X,And,Or,Not,Implies,Atom,Le,Lt,Ge,Gt,Eq- Term abstraction:
Termbase +Var/Const/ArgValue/CtxValue/UnaryFn/ArgLength. Comparison nodes accept anyTerm, so runtime-bound values can be compared against constants or against each other (Eq(ArgValue("refund", "amount"), CtxValue("approved_amount"))). Polymorphic dispatch + Hoare-vacuity semantics on missing values (undefined/nulloperands compare as false rather than throwing). - Recursive LTL evaluator with weak finite-trace semantics
- Per-event grounding for the core temporal/structural predicates,
plus
arg_value(tool, field)andctx_value(key)for Term-based lookups. - Det-only contract enforcement at the action boundary
- The five benchmark contract libraries (
sponsio:benchmark/redcode_exec,…/odcv_bench,…/tau2_bench,…/agentdojo,…/swebench) ship as YAML files under bothsponsio/contracts/benchmark/(Python) andts/packages/sdk/contracts/benchmark/(TypeScript). The YAML is the source of truth; both runtimes load and evaluate it identically. - Cross-language test scenarios in
tests/cross_language/scenarios.jsonpass on both runtimes
Both sides are det-only
The engine is deterministic on both Python and TS. So the parity gap discussed below is the gap between two det runtimes.
What's missing on the TS side
Formula nodes
| Node | Python | TS | Notes |
|---|---|---|---|
Subset (set-relation) | ✅ | ❌ | Used by data-intact and a few discovery paths. TS code that hits it throws unknown formula node. |
Patterns
The TS pattern library (ts/packages/sdk/src/core/patterns.ts) implements roughly 37 factories (now including workflowStep); Python (sponsio/patterns/library.py) has 46. The unimplemented in TS:
| Pattern | Why it matters |
|---|---|
no_pii(fields) | Output PII guard |
no_keywords(words) | Output content blacklist |
max_length(field, n) | Output length cap |
data_intact(action, paths) | Path immutability |
| Plus a few smaller helpers around content / data-flow |
Workaround: express these as raw Atom formulas, or run them through the Python guard via the OTEL bridge.
v0.2 enforcement strategies (partial parity)
The v0.2 redirect_to_safe pattern factory exists on TS (redirectToSafe in ts/packages/sdk/src/core/patterns.ts). It compiles to the same G(Not(called(unsafe))) LTL formula as the Python side, so a TS evaluator produces the same violation verdict as the Python verifier on a given trace.
What's NOT on TS yet:
- Strategy system + dispatch. The Python
DetFormulacarries anenforcement_strategyfield; the runtime monitor honours pattern-attached strategies (RedirectToSafe,EscalateToHuman,WarnOnly) before falling back toDetBlock. TS has no equivalent layer, so every violation surfaces as a plain block on TS. - LangGraph adapter redirect dispatch. Python's
LangGraphGuard._invoke_safe_toolinvokes the substitute tool transparently and records the safe call in the trace. The@sponsio/sdk/langchainadapter does not implement this; a redirect-style contract on TS today blocks the call, the application loop has to invoke the substitute manually. tool_policyconfig layer. Python'sBaseGuardsynthesizes atool_allowlistcontract from atool_policy: { default: deny, approved: [...] }block. TS does not yet have this convenience; you build the sametoolAllowlist([...])formula by hand.filter_tools(candidates)API +dry_runprobe. Python's per-turn proactive filter is a pure probe with no log / callback / perf pollution. TS does not yet expose this. Adapterwrap()-time filtering on TS is also not implemented; the same effect can be achieved by filtering the tool list before passing towrapTools.EscalateToHumannotifier callbacks. Python'sEscalateToHuman(notify=[...])fires user-supplied notifiers with isolation. TS lacks the strategy class entirely (see above).
Workaround: for redirect-style contracts on TS today, read the violation outcome and dispatch the substitute tool from your application loop; the formula side fires correctly.
Grounding predicates (atoms)
TS grounding covers the action-layer predicates: called, count, consecutive_count, called_with, arg_has, arg_field_has, arg_paths_within, plus a handful more (about 12 total).
Python additionally grounds the LLM-observation layer:
prompt_contains(text)llm_said(text)output_has(field)system_prompt_presentcontext_length(n)flow(src, dest)data-flow predicatesperm(P)permission predicatesdata_storesforward-propagation
Any contract that uses one of these atoms must run on the Python guard. This is the bigger of the two parity gaps in practice. Most LLM-observation contracts cannot currently be enforced from TS alone.
NL parser
TS's parseNl() (ts/packages/sdk/src/core/nl-parser.ts) recognises 8 patterns: must_precede, always_followed_by, rate_limit, idempotent, mutual_exclusion, no_reversal, cooldown, deadline.
Python's parse_nl_unified() recognises all 46 deterministic patterns including workflow_step and the Term comparison forms (ArgValue(...), CtxValue(...), ArgLength(...)). NL strings that do not match one of the 8 TS patterns return a parse failure on TS. Callers should fall back to constructing the DetFormula directly via the pattern factory (which IS available for workflowStep and the Term subclasses on TS), or parse on the Python side.
CLI surface
Both Python and TS ship a CLI with the same command set (about 20 subcommands each, including onboard, scan, validate, check, doctor, demo, report, packs, patterns, init, mode, explain, replay, export, export-sessions, eval, skill, prompt). Cross-language scenarios in tests/cross_language/ validate identical verdicts.
Roadmap
Track issues tagged area:ts-parity for status. Priority order:
- LLM-observation atoms (
prompt_contains,llm_said,output_has). Biggest user-visible gap. - v0.2 strategy system port:
EnforcementStrategyprotocol,RedirectToSafedispatch in the@sponsio/sdk/langchainadapter,EscalateToHuman.notifycallback hooks. - v0.2
tool_policyconfig block +filter_tools(candidates)API parity. - Missing patterns (
no_pii,no_keywords,max_length,data_intact). - Expand TS NL parser to match Python's surface, including
workflow_stepandTerm(ArgValue/CtxValue/ArgLength) comparison forms (factories exist on TS; NL recognition does not). Subsetnode + data-flow predicates.- DFA-compiled evaluator port. Python ships both a recursive
evaluator and a DFA-compiled fast path
(
sponsio/formulas/dfa_evaluator.py). TS has only the recursive evaluator. Verdicts agree on both, but the DFA path is faster on long traces.
What to do today if you need a missing feature
- Run the Python guard alongside the TS app. The OTEL bridge accepts traces from either runtime; mixed deployments work.
- Construct the AST manually. If the missing piece is a pattern
that compiles to existing TS nodes, you can build the
Formuladirectly. Patterns are just factories. - File an issue. Real user demand reorders the roadmap.