Parity Matrix — Python ↔ TypeScript Remote Client
Purpose: Settle the TypeScript client's contract now, from the Python reference
implementation, so that when it is built (per ADR 0008's re-entry condition, docs/adr/0008-
typescript-client-deferred.md in the repository — excluded from the published site alongside the
rest of docs/adr/) the work is translation, not negotiation.
Status: Every TypeScript entry is TS: planned — none of it exists in this repository.
There is no clients/typescript/ directory and no npm package.
Owner/status: SDK maintainers · Normative for the TS client's eventual shape · last reviewed
2026-07-23 (P9.8, alongside korchestrator.remote's Python implementation completing in P9.1–P9.7).
How to read this
- Python is the actual, shipped signature (
korchestrator.remote.KorchestratorClient,
[remote] extra).
- TypeScript (planned) is the settled intended name/signature — camelCase per ADR 0008,
otherwise identical semantics unless a Note says otherwise.
- Every row is
TS: planned. There are no partial or in-progress rows in this matrix — the
TypeScript client has not been started.
- Where the original TS-facing design sketch (spec 04 §7, as summarized in ADR 0008's context)
disagreed with what the Python reference implementation actually does, the Python
implementation wins — ADR 0008 states this reconciliation rule explicitly ("Where the spec and
the Python client disagree, the discrepancy is resolved and the spec updated"). Each such case is
called out in a Note.
Constructor
| Python |
TypeScript (planned) |
Status |
Notes |
KorchestratorClient(base_url, *, api_key=None, timeout=30.0, max_retries=3) |
new KorchestratorClient(baseUrl, { apiKey?, timeout?, maxRetries? }) |
TS: planned |
One apiKey option covers both a static key and a Keycloak/KIAM JWT (spec 04 §7.2 — one Authorization: Bearer header, one code path). An earlier TS-facing sketch split this into mutually exclusive apiKey/accessToken options; the Python implementation's single-parameter shape is authoritative per ADR 0008's reconciliation rule, and the TS options object should follow it. |
client.close() / await client.aclose() |
client.close() (sync) / await client.close() (async overload), or a single async dispose() under Symbol.asyncDispose |
TS: planned |
Python offers both a sync and an async close because Python has no native async-dispose protocol convention as ubiquitous as JS's; TS should prefer await using client = new KorchestratorClient(...) (Symbol.asyncDispose) as the idiomatic equivalent of Python's async with. |
async with KorchestratorClient(...) as client: |
await using client = new KorchestratorClient(...) |
TS: planned |
|
Run lifecycle (spec 04 §7.3/§7.4)
| Python |
TypeScript (planned) |
Status |
Notes |
run(objective, *, max_supersteps=10, mock_mode=False, tenant_id=None, timeout=None) -> RemoteRunResult |
run(objective, options?) -> Promise<RemoteRunResult> |
TS: planned |
Python's keyword-only optional args become one options object, per the naming vocabulary's run (never execute/launch/invoke/start). |
run_swarm(agents, edges=(), *, objective, max_supersteps=10, tenant_id=None, timeout=None) -> RemoteRunResult |
runSwarm(agents, edges, options?) -> Promise<RemoteRunResult> |
TS: planned |
agents: AgentConfig[]; edges: [string, string][]. |
get_run(run_id, *, timeout=None) -> RemoteRunResult |
getRun(runId, options?) -> Promise<RemoteRunResult> |
TS: planned |
|
wait(run_id, *, poll_interval=2.0, timeout=None) -> RemoteRunResult |
wait(runId, options?) -> Promise<RemoteRunResult> |
TS: planned |
Polls getRun until terminal, same as Python; does not return early on governance_paused. |
run_and_wait(objective, *, max_supersteps=10, mock_mode=False, tenant_id=None, poll_interval=2.0, timeout=None) -> RemoteRunResult |
runAndWait(objective, options?) -> Promise<RemoteRunResult> |
TS: planned |
Naming vocabulary: run_and_wait, never run_sync/await_result. |
list_runs(*, tenant_id=None, timeout=None) -> tuple[RunSummary, ...] |
listRuns(options?) -> Promise<RunSummary[]> |
TS: planned |
|
get_run_summary(run_id, *, timeout=None) -> RunSummary |
getRunSummary(runId, options?) -> Promise<RunSummary> |
TS: planned |
|
Control (spec 04 §7.3)
| Python |
TypeScript (planned) |
Status |
Notes |
resume(run_id, *, timeout=None) -> RemoteRunResult |
resume(runId, options?) -> Promise<RemoteRunResult> |
TS: planned |
|
cancel(run_id, *, timeout=None) -> RemoteRunResult |
cancel(runId, options?) -> Promise<RemoteRunResult> |
TS: planned |
|
edit_resume(run_id, *, updates=None, trust_delta=0.0, timeout=None) -> RemoteRunResult |
editResume(runId, options?) -> Promise<RemoteRunResult> |
TS: planned |
updates?: Record<string, JSONValue>. |
Identity and usage (spec 04 §7.3)
| Python |
TypeScript (planned) |
Status |
Notes |
me(*, timeout=None) -> CallerIdentity |
me(options?) -> Promise<CallerIdentity> |
TS: planned |
|
my_quota(*, timeout=None) -> Quota |
myQuota(options?) -> Promise<Quota> |
TS: planned |
|
my_runs(*, timeout=None) -> tuple[RunSummary, ...] |
myRuns(options?) -> Promise<RunSummary[]> |
TS: planned |
|
Key management (spec 04 §7.3, korchestrator:admin scope)
| Python |
TypeScript (planned) |
Status |
Notes |
create_key(*, scopes=(), timeout=None) -> ApiKey |
createKey(options?) -> Promise<ApiKey> |
TS: planned |
ApiKey.key is a SecretStr in Python (never logged/printed by default); the TS ApiKey.key is a plain string since JS has no comparable wrapper type in common use — callers must be told explicitly (JSDoc) not to log it. |
list_keys(*, timeout=None) -> tuple[ApiKeySummary, ...] |
listKeys(options?) -> Promise<ApiKeySummary[]> |
TS: planned |
Never carries the secret, in either language. |
revoke_key(key_id, *, timeout=None) -> None |
revokeKey(keyId, options?) -> Promise<void> |
TS: planned |
|
Discovery (spec 04 §7.3)
| Python |
TypeScript (planned) |
Status |
Notes |
tools(*, timeout=None) -> tuple[ToolDescriptor, ...] |
tools(options?) -> Promise<ToolDescriptor[]> |
TS: planned |
|
models(*, timeout=None) -> tuple[ModelCard, ...] |
models(options?) -> Promise<ModelCard[]> |
TS: planned |
Python reuses the local kernel's own ModelCard; TS should define one ModelCard type shared the same way, not a second shape. |
swarm_templates(*, timeout=None) -> tuple[SwarmTemplate, ...] |
swarmTemplates(options?) -> Promise<SwarmTemplate[]> |
TS: planned |
|
Streaming (spec 04 §7.3/§7.5)
| Python |
TypeScript (planned) |
Status |
Notes |
stream(run_id, *, timeout=None) -> AsyncIterator[RunEvent] (native async generator — the one method that is not a sync wrapper) |
stream(runId, options?): AsyncIterable<RunEvent> |
TS: planned |
Idiomatic in both languages: async for event of client.stream(runId) / for await (const event of client.stream(runId)). Python's reconnect semantics (full-jitter backoff, retry budget reset per successful reconnect, no Last-Event-ID resumption — the wire format carries no event id) must be matched exactly; this is a documented limitation, not a Python-specific shortcut, so the TS client should not silently promise more than Python delivers. |
Errors (spec 04 §7.5)
| Python |
TypeScript (planned) |
Status |
Notes |
korchestrator.exceptions.ApiError(message, *, status, code=None, trace_id=None) — a KorchError subclass with .status: int, .code: str, .trace_id: str \| None |
class ApiError extends KorchError { status: number; code: string; traceId?: string } |
TS: planned |
One error type for every failed KorchestratorClient call in both languages — never a raw transport (httpx/fetch) exception. NetworkError/TimeoutError (Python) cover connection-level failures (no response received) the same way in both languages — a real response with a non-2xx status is always ApiError; a dropped connection is NetworkError/its TS equivalent. |
KorchestratorClient._request's retry policy: 3 attempts, full-jitter exponential backoff, retry on 429/502/503/504 and connection failures, never any other 4xx |
Same policy, same status codes |
TS: planned |
ADR 0008's own context section mentions "retry on 429 and 503 but never 4xx" — an earlier, less precise sketch. Spec 04 §7.5's 429/502/503/504 list is what the Python client actually implements and is authoritative; the TS client should match the Python behavior, not the ADR's paraphrase. |
Models (spec 05, spec 04 §7)
Python (korchestrator.models.remote / korchestrator.models.routing) |
TypeScript (planned) |
Status |
Notes |
RemoteRunResult |
RemoteRunResult |
TS: planned |
status is the normalized string RunStatus, never the engine's raw numeric code, in both languages. |
RunSummary |
RunSummary |
TS: planned |
|
RunEvent |
RunEvent |
TS: planned |
|
CallerIdentity |
CallerIdentity |
TS: planned |
|
Quota |
Quota |
TS: planned |
|
ApiKey |
ApiKey |
TS: planned |
See the key-management note above re: SecretStr vs plain string. |
ApiKeySummary |
ApiKeySummary |
TS: planned |
|
ToolDescriptor |
ToolDescriptor |
TS: planned |
input_schema (not schema — reserved by Python's pydantic.BaseModel); TS has no equivalent reservation, but inputSchema should still be used for cross-language consistency. |
SwarmTemplate |
SwarmTemplate |
TS: planned |
|
Re-entry condition
Per ADR 0008, building the TypeScript client requires both:
- A named consumer needs it (a specific product/partner use case).
- The remote contract above has been stable across at least one minor release — no breaking
change to endpoints, auth, error shape, or method vocabulary.
Until then, this document is the settled target — not a promise of a delivery date.