Julep — durable, composable AI agents on Temporal.
$git clone https://github.com/julep-ai/julepInstalls into the current project.
Install julep by running `git clone https://github.com/julep-ai/julep`, then use it for the current task and follow its documentation at https://github.com/julep-ai/julep.
| 1 | # Julep |
| 2 | |
| 3 | Julep — durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step. |
| 4 | |
| 5 | Julep builds agents as **composable, durable dataflows** instead of ad-hoc loops: flows can crash and resume, retry safely, explain every step through a derived projection, and deny any tool the model was not explicitly allowed to call. The primary authoring surface is define-by-construction `@flow`: ordinary Python names graph steps while registered tools, pures, reasoners, branches, fan-out, retries, and timeouts compile to the same frozen wire-format IR. The pure core stays dependency-free, while the Temporal layer is optional. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Install |
| 10 | |
| 11 | ```bash |
| 12 | pip install --pre julep |
| 13 | ``` |
| 14 | |
| 15 | Julep 3 currently ships as a release candidate, so the `--pre` flag is required; it drops once `3.0.0` is final. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Quickstart |
| 20 | |
| 21 | 10 minutes, no API key. Install the base package and run this as a normal Python script: |
| 22 | |
| 23 | ```python |
| 24 | from typing import TypedDict |
| 25 | |
| 26 | from julep import Reasoner, deploy, flow, pure, think, tool |
| 27 | |
| 28 | |
| 29 | class SupportReply(TypedDict): |
| 30 | reply: str |
| 31 | |
| 32 | |
| 33 | @tool(effect="read", idempotent=True) |
| 34 | def lookup_ticket(ticket: str) -> dict[str, str]: |
| 35 | return { |
| 36 | "ticket": ticket, |
| 37 | "queue": "billing", |
| 38 | "summary": "Use the duplicate-charge runbook.", |
| 39 | } |
| 40 | |
| 41 | |
| 42 | @pure("ticket_prompt") |
| 43 | def ticket_prompt(hit: dict[str, str]) -> dict[str, str]: |
| 44 | return {"queue": hit["queue"], "context": hit["summary"]} |
| 45 | |
| 46 | |
| 47 | support_reply = Reasoner( |
| 48 | name="support_reply", |
| 49 | model="anthropic:claude-haiku-4-5-20251001", |
| 50 | system="Draft one concise support reply as JSON.", |
| 51 | reply=SupportReply, |
| 52 | ) |
| 53 | |
| 54 | |
| 55 | @flow |
| 56 | def triage(ticket: str) -> dict[str, str]: |
| 57 | hit = lookup_ticket(ticket, retries=2, timeout_s=5) |
| 58 | prompt = ticket_prompt(hit) |
| 59 | answer = think(support_reply, prompt, timeout_s=10) |
| 60 | return hit | answer |
| 61 | |
| 62 | |
| 63 | def fake_support_reply(value: dict[str, str]) -> SupportReply: |
| 64 | return {"reply": f"{value['queue']}: {value['context']}"} |
| 65 | |
| 66 | |
| 67 | deployment = deploy(triage, tools=[lookup_ticket], reasoners=[support_reply]) |
| 68 | result = deployment.dry_run( |
| 69 | "Customer was charged twice.", |
| 70 | reasoners={"support_reply": fake_support_reply}, |
| 71 | ) |
| 72 | |
| 73 | print(result.value) |
| 74 | ``` |
| 75 | |
| 76 | `@flow` runs once at definition time with data handles. Registered tools, registered pures, `think(...)`, `cond(...)`, `switch(...)`, `each(...)`, and `reschedule(...)` append graph steps instead of doing runtime work; `|` merges records and `h["key"]` plucks fields. `deploy(..., tools=..., reasoners=...)` freezes the tool and reasoner surface, and `dry_run(...)` executes locally with in-memory tools and deterministic fake reasoners. See the larger `@flow` examples in `examples/episode_summary_flow.py` and `examples/cluster_labeling_flow.py`. |
| 77 | |
| 78 | --- |
| 79 | |
| 80 | ## The CLI |
| 81 | |
| 82 | `julep` is the developer CLI for a whole **module of agents** — "dbt for agents, terminal-native." Point it at a directory; it discovers every `@flow`/`Agent(...)`, treats each as a node in a cross-agent graph, and gives you one selection grammar across every verb: |
| 83 | |
| 84 | ```bash |
| 85 | julep ls # list agents (name · kind · tags) |
| 86 | julep show triage # one agent's kind, source location, tags, calls |
| 87 | julep graph # the cross-agent DAG as Graphviz DOT |
| 88 | julep run triage --input '"TICKET-42"' # execute locally, stream the trace tree |
| 89 | julep lint +triage # validate an agent and everything it depends on |
| 90 | julep test triage # run pytest for the selected agents |
| 91 | julep trace <run-id> # render a cached run's trace tree + Langfuse link |
| 92 | julep doctor # preflight: discovery, git, Langfuse, Temporal |
| 93 | julep deploy triage --env staging # freeze → publish → record in the deploy ledger |
| 94 | ``` |
| 95 | |
| 96 | Selectors compose: `tag:support`, `state:modified` (Slim-CI), `+agent`/`agent+`/`@agent` graph traversal, `a,b` intersection, `--exclude`. Full reference: **[docs-site/content/doc |