Temporal is a general-purpose durable execution platform with seven official SDKs (Go, Java, Python, TypeScript, Ruby, PHP, and .NET). It has been in production for a decade, and if your agent needs to survive a crash mid-run, pick up a wait hours later, or coordinate retries across a polyglot fleet, that’s Temporal’s job — and it’s good at it.
Kitaru doesn’t do durable execution. It answers a different question: once a run happened, can you re-run it? Kitaru records an agent’s runs as sessions — through a framework adapter or by importing the traces you already collect — and replays them against your real code, so a model swap or a prompt change gets tested against what production actually saw, not a hunch.
The two aren’t in competition. An agent whose durability comes from Temporal can still be recorded and replayed by Kitaru: Temporal keeps it alive in production, and Kitaru tells you whether the change you’re about to ship helps or regresses.
Use Kitaru if you are
- Shipping a change to an agent's model, prompt, or code and want to test it against real production runs, not a vibe check
- Recording agent runs (via a framework adapter, or by importing Langfuse / LangSmith / Braintrust / OTel traces) so tool calls replay from the recording instead of hitting real systems
- Turning the sessions that caught a regression into a cohort that gates every future change
- Self-hosting your eval infrastructure so traces and credentials stay in your own systems
Use Temporal if you are
- Running a polyglot fleet (Go, Java, Python, TypeScript, Ruby, PHP, .NET) that needs one durability contract across all of it
- Keeping an agent, or any workflow, alive across crashes, retries, and hours-long waits in production
- Running general workflows — billing, provisioning, ETL, saga patterns — not specifically agent evals
- Leaning on Temporal's decade of production hardening, cron, namespacing, and a mature Web UI
Temporal keeps your agent alive when things go wrong in production. Kitaru tells you whether the change you're about to ship makes things go wrong less often.
Different layers
Temporal is asking: how do I keep this workflow alive through crashes, retries, and hours-long waits? Kitaru is asking: once this agent ran in production, which of those runs can I re-run — and did my change actually help?
In practice that means Temporal keeps orchestrating your agent exactly as it does today. Kitaru sits beside it: an adapter or a trace import turns each run into a session, and a session replays.
Recording, not instrumenting
A Temporal Activity that calls a model is just Python — you own the instrumentation, the token accounting, the retry policy. Kitaru doesn’t touch how Temporal runs your agent. It sits at the framework boundary: wrap the agent with a Kitaru adapter (PydanticAI, LangGraph, OpenAI Agents SDK, Mastra, Vercel AI SDK), or import the traces you already send to Langfuse, LangSmith, or Braintrust, and every model call and tool call lands as a node on a session — model, tokens, latency, cost, no separate logging setup.
await call_openai(prompt)unstructured by default · instrument it yourself- model
- gpt-5.4 · anthropic
- recorded
- model call + tool call, as nodes
From one session to a regression suite
One replayed session answers a question about one run. Freeze the sessions that matter into a cohort, and an experiment replays that whole cohort with one thing changed — a model, a prompt — and compares the two runs side by side. That’s the regression suite: not hand-picked test cases, production traffic you’ve already paid for.
What makes Kitaru unique
| Feature | Kitaru | Alternative |
|---|---|---|
| Durable execution — recovers a workflow after failure | Not supported | Yes |
| Pause / resume across hours-long waits with no active compute | Not supported | Yes |
| Polyglot production SDKs (Go, Java, TypeScript, Ruby, PHP, .NET) | Not supported | Yes |
| Native cron scheduling and namespacing | Not supported | Yes |
| Records agent runs as replayable sessions (adapter or trace import) | Yes | Not supported |
| Replays a session against real code, tool calls answered from the recording | Yes | Not supported |
| Cohorts: frozen session sets as regression suites | Yes | Not supported |
| Experiments: same cohort, one variable moved, compared | Yes | Not supported |
| Imports Langfuse / LangSmith / Braintrust / OTel traces | Yes | Not supported |
| Self-hosted, open source | Yes | Yes |
How the two surfaces map
| Concept | Temporal | Kitaru |
|---|---|---|
| Layer | Durable execution (keeps the agent alive in prod) | Replay-based evals (tests a change to the agent) |
| Core unit | Workflow / Activity | Session — recorded run, re-executable |
| Recording | N/A — Temporal orchestrates, doesn’t record for eval | A framework adapter wraps the agent, or traces are imported |
| Replay | Deterministic replay of Workflow Event History, for recovery | Re-execute a session against real code; tool calls answered from the recording |
| “Did my change help?” | Not what Temporal answers | Two runs over the same cohort, diffed |
| Where it runs | Temporal Service + Workers (self-hosted or Temporal Cloud) | Self-hosted server; workers replay in your environment |
Code comparison
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,
)
# Temporal 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)from datetime import timedelta
from temporalio import activity, workflow
from temporalio.client import Client
from temporalio.worker import Worker
@activity.defn
async def research(topic: str) -> str:
return await call_llm(f"Research: {topic}")
@activity.defn
async def draft(brief: str) -> str:
return await call_llm(f"Write a draft:\n{brief}")
@workflow.defn
class ReviewFlow:
def __init__(self) -> None:
self._approved: bool | None = None
@workflow.signal
def approve(self, ok: bool) -> None:
self._approved = ok
@workflow.run
async def run(self, topic: str) -> str:
brief = await workflow.execute_activity(
research, topic, start_to_close_timeout=timedelta(minutes=5)
)
text = await workflow.execute_activity(
draft, brief, start_to_close_timeout=timedelta(minutes=5)
)
await workflow.wait_condition(lambda: self._approved is not None)
return text if self._approved else "Rejected"
# Run via: await client.execute_workflow(ReviewFlow.run, topic,
# id=..., task_queue=...); approval arrives via
# client.get_workflow_handle(...).signal(ReviewFlow.approve, True).Put replay-based evals under your Temporal-run agents
If your durability problem spans Go services, Java backends, and cron-scheduled ETL, Temporal is the right tool, and its production track record backs that up. If the question is whether the change you’re about to ship to a Temporal-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.
uv add "kitaru[cli,worker]" kitaru-pydantic-ai