Compare

Kitaru vs Restate: different layers, not competing runtimes

Restate is a polyglot durable runtime for services, Virtual Objects, and workflows. Kitaru records what agent runs did and replays them as evals. Different questions — and they compose.

uv add "kitaru[cli,worker]" kitaru-pydantic-ai
Sign up freeRead the docs

Restate is a serious durable runtime. Polyglot SDKs across TypeScript, Java, Kotlin, Go, Rust, and Python. Three handler shapes (services, Virtual Objects with keyed per-entity state and strongly-consistent access, and workflows), journaled execution, awakeables for human-in-the-loop, OpenTelemetry tracing, self-hostable on every major cloud. If your durability problem is polyglot and backend-services-shaped, Restate is a strong, honest answer.

Kitaru isn’t a durable runtime at all. It answers a different question: once an agent run happened — durable or not — can you re-run it, and know whether a change made it better or worse? Kitaru records runs as sessions, through a framework adapter or by importing the traces you already collect, and replays them against your real code.

The two layers don’t compete. A Restate-run agent can still be wrapped by a Kitaru adapter, or have its traces imported — Restate keeps it alive in production, Kitaru tells you whether the next change to it is safe to ship.

Kitaru

Use Kitaru if you are

  • Testing a model, prompt, or code change to a Restate-run agent against real production sessions instead of a vibe check
  • Recording agent runs via a framework adapter (PydanticAI, LangGraph, OpenAI Agents SDK, Mastra, Vercel AI SDK), or importing traces from Langfuse, LangSmith, Braintrust, or OTel
  • Freezing the sessions that caught a regression into a cohort that gates every future change
  • Self-hosting the eval infrastructure so traces and credentials stay in your own systems
Alternative

Use Restate if you are

  • Running a polyglot services stack (TypeScript, Java, Kotlin, Go, Rust, Python) that needs one durability primitive across all of it
  • Doing durable RPC, sagas, or long-running backend workflows — not testing changes to an agent
  • Modeling keyed per-entity state with strongly-consistent access (chat sessions, orders, devices, users) as a first-class abstraction
  • Fine running `restate-server` as infra and using awakeables / durable promises as the core HITL primitive
Restate keeps your services — agents included — durable in production. Kitaru replays what they did in production to prove whether your next change helps.

Different layers

Defaults tell you what a tool is actually for. Restate’s defaults are services, Virtual Objects, and workflows — real durability primitives, shaped for backend app workloads. Kitaru doesn’t have durability primitives at all. What it has is a way to turn a run that already happened into something you can re-run and score.

Restate · app-shapedHandlers for services, state, and workflows
serviceRPC-shaped durable handlers
virtual objectkeyed per-entity state, strongly consistent
workflowlong-running, journaled, resumable
Durability primitives, built for app workloads.
KitaruReplay-based evals for agent runs
adapterwraps your agent, no rewrite
sessionevery run recorded — model calls, tool calls, tokens, cost
replayre-executes against real code; recording answers tool calls
cohort → experimentsame sessions, one variable moved, compared
Adapters ship for PydanticAI, LangGraph, OpenAI Agents SDK, Mastra, and Vercel AI SDK.
  • What Kitaru ships: an adapter that wraps your agent with no rewrite, a session recorded on every run, and replay with cohorts and experiments on top.
  • Framework adapters: PydanticAI, LangGraph, the OpenAI Agents SDK, Mastra, and the Vercel AI SDK ship today; anything else comes in through a trace import.
  • Both are real tools, aimed at different problems. What ships in the box tells you what the team was watching when they set the defaults.

From one session to a regression suite

Both tools persist state, for different reasons. Worth saying clearly.

experiment · pr-311gpt-5-mini
session s_9f2a1,120 tok · 2.1s
refund-check
session s_71cd3,640 tok · 5.4s
refund-check
diffsame cohort
experiment · v1gpt-5.4
session s_9f2a1,247 tok · 2.8s
refund-check
session s_71cd3,980 tok · 6.1s
refund-check
Restate journals handler progress and keys Virtual Object state per entity — durable state for a running service. Kitaru's sessions are recordings of finished runs, kept so they can be replayed — different job, different data.
  • Restate: handler progress is journaled for deterministic replay during recovery. Virtual Objects expose keyed per-entity state with strongly-consistent access and single-writer semantics per object — the right shape for “the chat session,” “the order,” “the device.”
  • Kitaru: a session is a recording of a finished run. Freeze the sessions that matter into a cohort, replay that cohort with one thing changed, and compare the two runs side by side — the regression suite that catches the next bad deploy.
  • Honest read: VO-keyed per-entity state and replayable sessions aren’t two answers to the same question. One is state for a run in progress; the other is a recording of a run that’s done.

One adapter, many frameworks

Restate’s polyglot reach is real — six languages, one durability contract. Kitaru is narrower by design: it meets agents where most teams are actually building them today.

your agentwrapped once by a Kitaru adapter
record + replaythe agent's own code runs, unchanged
PydanticAIKitaruAgent
LangGraphKitaruGraphRunner
OpenAI AgentsKitaruRunner
Mastra · VercelTS adapters
Restate: handler SDKs ship in six languages, from the runtime up. Kitaru's adapters cover today's most common Python and TypeScript agent frameworks, with a trace-import path (Langfuse, LangSmith, Braintrust, OTel) for anything else.
  • Wrap it once: KitaruAgent(agent, agent_id=...) wraps a PydanticAI, LangGraph, or OpenAI Agents SDK agent with no rewrite. Mastra and the Vercel AI SDK get native-signature TypeScript adapters.
  • No adapter for your framework? Import the traces you already send to Langfuse, LangSmith, or Braintrust, or emit OTLP and import that instead. Either path lands as a replayable session.
  • The trade: polyglot backend teams often want exactly Restate’s model. Agent teams usually want their existing framework wrapped once, not a new one to adopt.

Embedded recording vs server-registered handlers

Both tools have a server. The wedge is whether your agent’s request path has to cross it.

Restate · server-registeredRequest path flows through the server
caller
restate-serverRust · BSL 1.1
journalroutingreplay
handlerhandler
Handlers register with the server. Every invocation crosses the server boundary.
Kitaru · embeddedRecording runs in your process; server holds metadata
your python process
KitaruAgent(agent)
run_sync(...)
→ session
uv add "kitaru[cli,worker]" kitaru-pydantic-ai · no server process in the request path
kitaru servermetadata · UI · auth
Server holds metadata and drives replay; recording happens in your process.
  • Kitaru: uv add kitaru, wrap the agent with an adapter, and it runs in your process exactly as before. The Kitaru server stores session metadata and powers replay, the UI, the CLI, and auth — it isn’t a proxy every invocation crosses.
  • Restate: handlers register with restate-server, and invocations flow through it. The server journals progress, routes requests, handles retries, and drives replay. That’s not incidental; it’s the durability model.
  • Different jobs, different shapes. Restate’s server is in the request path because durability requires it. Kitaru’s server holds metadata because recording doesn’t.

What makes Kitaru unique

FeatureKitaruRestate
Durable execution and replay for production servicesNot supportedYes
Virtual Objects: keyed per-entity state with strongly-consistent accessNot supportedYes
Awakeables and durable promises for human-in-the-loopNot supportedYes
Polyglot handler SDKs (TypeScript, Java, Kotlin, Go, Rust, Python)Not supportedYes
OpenTelemetry tracing (native instrumentation)Not supportedYes
Records agent runs as replayable sessions (adapter or trace import)YesNot supported
Replays a session against real code, tool calls answered from the recordingYesNot supported
Cohorts: frozen session sets as regression suitesYesNot supported
Experiments: same cohort, one variable moved, comparedYesNot supported
Deterministic + model-graded evaluatorsYesNot supported
Imports Langfuse / LangSmith / Braintrust / OTel tracesYesNot supported
Self-hostable (Kitaru: Apache 2.0; Restate: BSL 1.1, Apache 2.0 as the change license)YesYes

How the two surfaces map

ConceptRestateKitaru
LayerDurable execution for services (keeps handlers alive in prod)Replay-based evals (tests a change to the agent)
Durable unitHandler (Service / Virtual Object / Workflow)Session — recorded run, re-executable
Per-entity stateVirtual Object (keyed, strongly-consistent)Not applicable — Kitaru doesn’t hold production state
Human-in-the-loopAwakeable / durable promiseNot applicable to replay; recorded if the original run had one
RecordingN/A — Restate orchestrates, doesn’t record for evalA framework adapter wraps the agent, or traces are imported
“Did my change help?”Not what Restate answersTwo runs over the same cohort, diffed
DeploymentSelf-hosted restate-server + registered handlersAdapter embedded in your process; server + workers self-hosted

Code comparison

Restate + KitaruRecommended
from pydantic_ai import Agent
from kitaru_pydantic_ai import KitaruAgent

reviewer = KitaruAgent(
  Agent("openai:gpt-5.4", system_prompt="You're a compliance reviewer."),
  agent_id=AGENT_ID,
)

# Restate keeps this durable in production.
# Every run also lands as a Kitaru session.
result = reviewer.run_sync(case)

# Later: freeze the sessions that matter, test a change.
import kitaru

client = kitaru.KitaruClient()
cohort = client.cohorts.create("hard-cases", sessions=session_ids)
experiment = client.experiments.create(
  "cheaper-model",
  model="gpt-5-mini",
  tool_policy=History(scope="cohort", on_miss="fail"),
)

before = experiment.run(cohort=cohort, version="v1")
after = experiment.run(cohort=cohort, version="pr-311")
client.compare(before, after)
Restate alone
import restate

review = restate.Workflow("ReviewWorkflow")

@review.main()
async def run(ctx: restate.WorkflowContext, topic: str) -> str:
  # Journaled step. Non-deterministic work lives inside ctx.run().
  brief = await ctx.run(
      "research",
      lambda: call_llm(f"Research: {topic}"),
  )
  text = await ctx.run(
      "draft",
      lambda: call_llm(f"Draft: {brief}"),
  )

  # Awakeable: durable promise resolved by an external caller.
  approval_id, approval = ctx.awakeable()
  send_approval_link(approval_id, text)
  approved = await approval
  return text if approved else "Rejected"

app = restate.app([review])

# Register the workflow with restate-server, deploy the handler,
# and resolve the awakeable from wherever approvals come in.

Put replay-based evals under your Restate-run agents

If your durability problem is shaped like a polyglot backend with keyed per-entity state, Restate is the right tool for that job. If the question is whether the change you’re about to ship to a Restate-run agent actually helps, Kitaru replays the sessions that already happened — record them once, via an adapter or an import, and every future change gets tested against real production behavior instead of a guess.

We’ve spent five years building the MLOps-ready version of this problem at ZenML. Kitaru is that same team, applied to the agent-eval layer.

uv add "kitaru[cli,worker]" kitaru-pydantic-ai
Sign up free