# Julep: Durable AI Agent Flows on Temporal

> Julep is a Python library for building AI agents as composable, durable dataflows that compile to a frozen wire-format IR. Flows can crash and resume on Temporal, retry safely, and deny tools the model was not explicitly allowed to call. It ships as a release candidate under Apache-2.0.

**julep-ai/julep** — Project brief: Julep, durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step.

- Repository: https://github.com/julep-ai/julep
- Website: https://docs.julep.ai
- Stars: 6,576 · Forks: 968
- Language: Python
- License: Apache-2.0
- Published: 2026-08-08 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/julep-ai-julep

## What Problem Julep Solves and Who It Targets

The standard approach to building an AI agent in Python is to write an async loop that calls a language model, parses the response, and invokes tools based on the output. This works for short tasks but becomes fragile over longer workflows: a network timeout midway through a multi-step pipeline either reruns the whole thing from scratch or requires custom checkpoint logic. Julep addresses this by compiling agent logic into an immutable intermediate representation and running it on Temporal, a durable execution engine that handles retries, failure recovery, and workflow state persistence. The target audience is Python developers building agents for production workloads where partial failure, exactly-once tool execution, and auditable execution traces are requirements, not nice-to-haves. The library is Apache-2.0 licensed and requires Python 3.12 or later.

## The @flow Compilation Model and Frozen IR

The primary authoring surface in Julep is the @flow decorator. When Python executes a function decorated with @flow, it does not run the function's body directly. Instead, each operation inside the body, calling a registered tool, invoking a Reasoner, branching with cond(), fanning out with each(), or scheduling a retry with reschedule(), appends a node to a directed acyclic graph. The pipe operator merges records and subscript notation plucks fields from data handles. When the graph construction is complete, deploy() compiles it to a frozen wire-format IR.

This design has a concrete consequence: the flow's tool surface is fixed at compile time. A model running inside the flow cannot call a tool that was not declared in the deploy() call's tools= argument. The README describes this as denying any tool the model was not explicitly allowed to call. For security-sensitive applications where prompt injection or unexpected model behavior could cause harm through tool misuse, this property is a real safeguard. The frozen IR is also what allows dry_run() to execute the flow locally using in-memory tools and deterministic fake reasoners without touching any external service.

## Installing and Running the Quickstart

Julep 3 is currently in release-candidate status. The --pre flag is required until the 3.0.0 final release:

```bash
pip install --pre julep
```

The README states that --pre drops once 3.0.0 is final. The base package depends only on PyYAML, typer, click, and jsonschema. Temporal support is a declared optional extra in pyproject.toml and requires temporalio and cryptography; DBOS-based durable execution is a separate optional extra. The quickstart example in the README needs no API key and runs as a plain Python script. It defines a tool with @tool, a transformation with @pure, a Reasoner, and a @flow that wires them together. The @flow runs once at definition time to build the graph; deploy() freezes the tool and reasoner surface. The dry_run() call executes the flow locally, which is sufficient for verifying logic without a Temporal cluster. The README points to examples/episode_summary_flow.py and examples/cluster_labeling_flow.py for larger worked examples that include MCP tool references.

## The Julep CLI: Development Workflow for Agent Modules

Julep ships a developer CLI that the README describes as "dbt for agents, terminal-native." The CLI treats a directory of @flow and Agent objects as a module and provides a consistent selection grammar across all its verbs. Key commands and what they do:

```bash
julep ls
```

Lists all agents with their name, kind, and tags.

```bash
julep run triage --input '"TICKET-42"'
```

Executes an agent locally and streams the trace tree.

```bash
julep graph
```

Outputs the cross-agent dependency graph in Graphviz DOT format, which is useful for understanding which agents call which.

```bash
julep lint +triage
```

Validates an agent and all agents it transitively depends on. The + prefix is a selector that traverses the dependency graph upstream from the named agent.

Selectors compose: tag:support selects by tag, state:modified limits to agents changed since the last deployment, and comma-separated names intersect results. This composition lets CI pipelines run tests only for the subset of agents that changed in a given commit, which the README calls Slim-CI.

## MCP Tool Support and the Frozen Snapshot Safety Model

Julep supports the Model Context Protocol for tool references inside @flow. The mcp_tool() helper creates a reference to a named tool on an MCP server. Before the flow runs, Julep fetches a snapshot of the MCP server's tool schemas and freezes them into the IR alongside the rest of the flow definition. This snapshot-backed approach has a specific safety property: if the tool's schema changes after the flow was deployed, the worker-side MCP preflight check compares the live schema against the frozen snapshot. If the schemas diverge, the run fails terminally with a typed surface drift error rather than silently using the new schema.

The README documents new releases as defaulting to exact pin comparison, with explicit names and off escape hatches for cases where schema evolution is intentional. Mid-run tool removal triggers the same terminal failure. This means that upgrading a dependency that exposes MCP tools requires a deliberate redeploy of any flows that reference those tools, which prevents silent behavioral changes from tool schema updates reaching running workflows.

## Secrets and the Operator Vault

The self-hosted Julep control plane includes a write-only operator vault for long-lived credentials. Values stored in the vault are encrypted at rest and are never readable after they are written. Short-lived, per-run credentials can also be bound to whole-string secret://name references in MCP header configurations; the README states that these values travel only in encrypted Temporal payloads and are excluded from stored run data and projections.

The deployment configuration lives in pyproject.toml under [tool.julep]. Each deployable environment section declares the Temporal address, a release store bucket, a worker image reference, and the worker's context factory. Worker environment variables that contain secrets are declared as Kubernetes Secret references in worker_secret_environment, meaning the actual secret values exist only in the worker at runtime and are not present in the configuration file. The payload_encryption_secret key is required for application releases and names an existing Kubernetes Secret that holds the keyring and active key ID for Temporal payload encryption.

## Limitations and When Julep Is the Wrong Fit

The most significant operational constraint is Python 3.12 as the minimum version. Projects running on Python 3.10 or 3.11 cannot use Julep without upgrading. The library is a release candidate, which means API-breaking changes are still possible before 3.0.0 goes final. The pyproject.toml file shows the current version as 3.0.0rc6.

Durable execution on Temporal requires either a Temporal cloud account or a self-hosted Temporal cluster. The base package works without Temporal (dry_run() uses in-memory execution), but production deployments that need crash recovery and exactly-once semantics require the temporal extra and a running Temporal service. For teams without Temporal infrastructure, the durability guarantees are unavailable without that investment. The README mentions a DBOS backend as an alternative durable execution layer (dbos extra), though it notes that DBOS requires Python 3.11 or later and the module raises an ImportError on earlier versions.

Julep also does not support streaming model responses within a flow. The Reasoner abstraction returns a complete reply contract validated against a jsonschema at runtime. Applications that need to stream tokens to a user interface while the flow is running must handle that outside the @flow boundary.

## Julep versus LangChain: Different Models for Agent Structure

LangChain is a Python framework for building LLM-powered applications through composable chain abstractions. It provides a large library of integrations for language models, vector stores, and tools, and its execution model runs the chain synchronously or asynchronously without compiling to an external IR. LangChain does not have built-in Temporal integration, which means long-running chains do not survive process restarts without custom checkpointing.

Julep's design philosophy is different in several ways. Flows compile to an immutable IR at definition time, which means the tool surface is fixed before execution begins. Retries and recovery are handled by Temporal rather than application code. The developer CLI treats the agent directory as a deployable module with plan, apply, and status verbs, which gives the development workflow a shape similar to infrastructure-as-code tooling. Teams choosing between the two should weigh whether they need Temporal's durability guarantees badly enough to accept the Temporal infrastructure requirement and the Python 3.12 constraint. For prototyping or simpler applications that do not require crash recovery, LangChain's broader ecosystem and looser requirements may be a better starting point.

## Conclusion

Julep is the right choice for teams building AI agents that must survive process restarts, retry individual steps without re-running the whole flow, or enforce a fixed tool surface that the underlying model cannot expand at runtime. The release-candidate status means the API is still changing; verify that your version of pyproject.toml pins julep to an exact release candidate before deploying to production. Teams that want a simpler chain-based approach without the Temporal dependency should compare Julep against LangChain before committing.

## FAQ

### Does Julep require a Temporal cluster to run agents?

Not for local development. The dry_run() function executes flows in memory without a Temporal connection. For production deployments that need crash recovery and safe retries, installing the temporal extra and connecting to a Temporal cluster is required.

### What Python version does Julep require?

Julep requires Python 3.12 or later. The pyproject.toml file in the repository states this as the minimum version. The DBOS backend extra is documented as resolvable on Python 3.10 but raises an ImportError at import time on earlier versions.

### How does @flow differ from a regular async Python function?

A regular async function runs its body when awaited. A @flow function runs once at definition time to build a graph of steps. Tools, reasoners, and control structures inside the body append nodes to the graph rather than executing immediately, and deploy() compiles that graph to a frozen IR.

## Sources

- [Official documentation](https://docs.julep.ai)
- [Official README](https://github.com/julep-ai/julep#readme)
- [Project repository](https://github.com/julep-ai/julep)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/julep-ai-julep
