{"id":13455909,"url":"https://github.com/julep-ai/julep","last_synced_at":"2026-10-20T20:27:40.370Z","repository":{"id":233453453,"uuid":"781914762","full_name":"julep-ai/julep","owner":"julep-ai","description":"Julep — durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step.","archived":false,"fork":false,"pushed_at":"2026-08-06T05:17:30.000Z","size":98941,"stargazers_count":6574,"open_issues_count":2,"forks_count":968,"subscribers_count":21,"default_branch":"main","last_synced_at":"2026-10-14T08:03:44.198Z","etag":null,"topics":["agents","ai","ai-agents","ai-agents-framework","ai-memory","ai-platform","aiagents","developer-tools","devfest","llm","llm-ops","node","node-js","nodejs","python"],"latest_commit_sha":null,"homepage":"https://docs.julep.ai","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/julep-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-04T09:33:02.000Z","updated_at":"2026-10-12T11:02:28.000Z","dependencies_parsed_at":"2025-08-26T22:24:59.083Z","dependency_job_id":"04d5d52e-7aa1-4408-9b41-ddaf0ef6ada6","html_url":"https://github.com/julep-ai/julep","commit_stats":null,"previous_names":["julep-ai/julep"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/julep-ai/julep","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julep-ai%2Fjulep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julep-ai%2Fjulep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julep-ai%2Fjulep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julep-ai%2Fjulep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/julep-ai","download_url":"https://codeload.github.com/julep-ai/julep/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julep-ai%2Fjulep/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344836268,"owners_count":38332409,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-17T02:00:06.547Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","ai-agents","ai-agents-framework","ai-memory","ai-platform","aiagents","developer-tools","devfest","llm","llm-ops","node","node-js","nodejs","python"],"created_at":"2024-07-31T08:01:13.350Z","updated_at":"2026-10-17T04:05:12.245Z","avatar_url":"https://github.com/julep-ai.png","language":"Python","funding_links":[],"categories":["Jupyter Notebook","Machine Learning and AI","Python","Papers","Built with Temporal","AI Agents","AI Orchestration \u0026 Deployment","AI Agent Frameworks \u0026 SDKs","\u003ca name=\"Jupyter%20Notebook\"\u003e\u003c/a\u003eJupyter Notebook","Agent Frameworks","4. Agentic AI \u0026 Multi-Agent Systems","*Ops for AI"],"sub_categories":["LLM and Inference","Autonomous Task Solver","Blog posts","Agent Frameworks and Runtimes","Workflow Orchestration for AI","Orchestration Frameworks","AI Orchestration \u0026 Deployment"],"readme":"# Julep\n\nJulep — durable, composable AI agents. Flows that crash and resume, retry safely, and explain every step.\n\nJulep 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.\n\n---\n\n## Install\n\n```bash\npip install --pre julep\n```\n\nJulep 3 currently ships as a release candidate, so the `--pre` flag is required; it drops once `3.0.0` is final.\n\n---\n\n## Quickstart\n\n10 minutes, no API key. Install the base package and run this as a normal Python script:\n\n```python\nfrom typing import TypedDict\n\nfrom julep import Reasoner, deploy, flow, pure, think, tool\n\n\nclass SupportReply(TypedDict):\n    reply: str\n\n\n@tool(effect=\"read\", idempotent=True)\ndef lookup_ticket(ticket: str) -\u003e dict[str, str]:\n    return {\n        \"ticket\": ticket,\n        \"queue\": \"billing\",\n        \"summary\": \"Use the duplicate-charge runbook.\",\n    }\n\n\n@pure(\"ticket_prompt\")\ndef ticket_prompt(hit: dict[str, str]) -\u003e dict[str, str]:\n    return {\"queue\": hit[\"queue\"], \"context\": hit[\"summary\"]}\n\n\nsupport_reply = Reasoner(\n    name=\"support_reply\",\n    model=\"anthropic:claude-haiku-4-5-20251001\",\n    system=\"Draft one concise support reply as JSON.\",\n    reply=SupportReply,\n)\n\n\n@flow\ndef triage(ticket: str) -\u003e dict[str, str]:\n    hit = lookup_ticket(ticket, retries=2, timeout_s=5)\n    prompt = ticket_prompt(hit)\n    answer = think(support_reply, prompt, timeout_s=10)\n    return hit | answer\n\n\ndef fake_support_reply(value: dict[str, str]) -\u003e SupportReply:\n    return {\"reply\": f\"{value['queue']}: {value['context']}\"}\n\n\ndeployment = deploy(triage, tools=[lookup_ticket], reasoners=[support_reply])\nresult = deployment.dry_run(\n    \"Customer was charged twice.\",\n    reasoners={\"support_reply\": fake_support_reply},\n)\n\nprint(result.value)\n```\n\n`@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`.\n\nUse `mcp_tool(server, tool)` inside `@flow` for an MCP reference. Its schemas\nand behavior contract come from the frozen MCP snapshot; handle-valued keyword\narguments build its input record directly. `examples/episode_summary_flow.py`\nshows snapshot-backed MCP reads and writes with a local `mcp_call` fake.\n\n## Production secrets and MCP safety\n\nThe self-hosted control plane includes a write-only operator vault whose values\nare encrypted at rest. Callers can also bind short-lived, per-run credentials to\nwhole-string `secret://name` MCP header references; those values travel only in\nencrypted Temporal payloads and are excluded from stored run data and\nprojections. Before user effects, worker-side MCP preflight checks the frozen,\ntransitively reachable tool surface. New releases default to exact `pin`\ncomparison, with explicit `names` and `off` escape hatches; mid-run tool removal\nor post-validation schema rejection fails terminally as typed surface drift.\n\nSee the **[control-plane guide](docs-site/content/docs/deploy/control-plane.md)**\nfor vault configuration, run-secret limits and binding rules, MCP policy\nbehavior, and the administration API.\n\n---\n\n## The CLI\n\n`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:\n\n```bash\njulep ls                                  # list agents (name · kind · tags)\njulep show triage                         # one agent's kind, source location, tags, calls\njulep graph                               # the cross-agent DAG as Graphviz DOT\njulep run triage --input '\"TICKET-42\"'    # execute locally, stream the trace tree\njulep lint +triage                        # validate an agent and everything it depends on\njulep test triage                         # run pytest for the selected agents\njulep trace \u003crun-id\u003e                      # render a cached run's trace tree + Langfuse link\njulep doctor                              # preflight: discovery, secret refs, git, Langfuse, Temporal\njulep deploy triage --env staging         # freeze → publish → record in the deploy ledger\njulep serve api --migrate                 # run the self-hosted FastAPI control plane\n```\n\nSelectors compose: `tag:support`, `state:modified` (Slim-CI), `+agent`/`agent+`/`@agent` graph traversal, `a,b` intersection, `--exclude`. Full reference: **[docs-site/content/docs/guides/using-the-cli.md](docs-site/content/docs/guides/using-the-cli.md)**.\n\nFor production applications, declare an explicit object instead of adding a\nsecond discovery convention:\n\n```python\nfrom collections.abc import Mapping\n\nfrom julep import Application, CapabilityManifest, McpSnapshot, PipelineSpec\n\n\ndef load_live_memory_tools_snapshot(\n    environment: Mapping[str, str],\n) -\u003e McpSnapshot:\n    # Use the selected deployment environment to call tools/list.\n    ...\n\napplication = Application(\"memory\", [\n    PipelineSpec(\n        name=\"episode_summary\",\n        flow=summary_flow,\n        reasoners=(summary_reasoner,),\n        capabilities=CapabilityManifest.from_file(\"summary-capabilities.yaml\"),\n        lane=\"summary\",\n        eval_packages=(\"prompts/episode_summary.ctx\",),\n        snapshot=memory_tools_snapshot,\n        snapshot_source=load_live_memory_tools_snapshot,\n    ),\n])\n```\n\nFor `julep plan`, `apply`, and application-level `status`, each\n`snapshot_source` receives a read-only mapping made from the selected\nenvironment's `[vars]` followed by `[worker_environment]` (worker values win on\nduplicate names). Secret-backed worker variables are intentionally absent:\n`worker_secret_environment` contains Kubernetes Secret references, not values,\nand those values exist only in the worker at runtime. The callback must return\nan `McpSnapshot`; pass any credential needed for schema discovery through a\nnon-secret control-plane mechanism rather than expecting a Secret value here.\n\nPoint `[tool.julep].application` at that object with a `module:attribute` value.\nEach deployable environment also names the worker's explicit context factory;\nordinary and Secret-backed worker environment can be reconciled with the lane:\n\n```toml\n[tool.julep.env.staging]\ntemporal_address = \"temporal-frontend.temporal.svc.cluster.local:7233\"\nrelease_store = \"s3://julep-releases/julep\"\nworker_image = \"registry.example/memory@sha256:\u003cdigest\u003e\"\nworker_context_factory = \"memory.worker:build_context\"\nworker_service_account = \"julep-worker\"\nworker_priority_class = \"julep-model-worker\"\npayload_encryption_secret = \"temporal-payload-codec\"\n\n[tool.julep.env.staging.worker_environment]\nMEMORY_TOOLS_MCP_URL = \"http://memory-tools/mcp-internal\"\nJULEP_BUNDLE_ALLOWED_SIGNERS = \"\u003c64-hex-ed25519-public-key\u003e\"\n\n[tool.julep.env.staging.worker_secret_environment.MEMORY_TOOLS_JWT_PRIVATE_KEY]\nsecret_name = \"memory-tools-jwt\"\nkey = \"private-key\"\n```\n\n`payload_encryption_secret` is required for application releases and names an\nexisting Kubernetes Secret in `kubernetes_namespace` with `keyring` and\n`active-key-id` entries. The PriorityClass is optional: set\n`worker_priority_class` only when shared cluster infrastructure provisions it\n(the EKS demo does); omit it on ordinary clusters.\n\n`julep plan --env staging` reports artifact, MCP-schema, Helm/KEDA, and runtime\ndrift; `julep apply --env staging` publishes an immutable S3-artifact store release and\nreconciles one digest-pinned Helm release per lane and immutable release on a\nrelease-specific task queue, without changing traffic;\n`julep status --env staging` aggregates the release and live lane state. That\napplication-level status path is selected only when `[tool.julep].application` is\nconfigured and no selector or `--exclude` is supplied; selected status queries\ncontinue to inspect the legacy per-agent deploy ledger.\n\nApplication publishing requires a 64-hex Ed25519 seed (or a file containing\none) in `JULEP_BUNDLE_SIGNING_KEY`. In production, set the corresponding 64-hex\npublic key in the non-secret `JULEP_BUNDLE_ALLOWED_SIGNERS` worker environment;\n`apply` rejects a configured allow-list that does not contain the publishing\nkey. Read-only `plan` and application-level `status` need that public allow-list\n(or the private key as a local fallback). Install `julep[store,temporal]` for\nS3 publication and Temporal workers, plus any pipeline-specific extras. The\ncontrol-plane host also needs authenticated `helm`, `kubectl`, and `temporal`\nCLIs; `apply --publish-only` skips Helm reconciliation.\n\n`julep worker` runs continuously from its environment contract. A positive\n`--smoke-test-seconds N` verifies Temporal connectivity, polls the configured\nqueue for `N` seconds, drains, and exits; the default `0` keeps serving.\n\n---\n\n## Extras\n\nThe base install is authoring + compile only (PyYAML). Optional extras add runtime surfaces:\n\n| Extra | `pip install --pre 'julep[...]'` | Adds |\n|---|---|---|\n| `temporal` | `julep[temporal]` | durable execution on Temporal (workflows, activities, worker, client helpers) |\n| `dbos` | `julep[dbos]` | durable execution on DBOS / Postgres (steps, flow workflow, chaining runner) |\n| `http` | `julep[http]` | native HTTP tool calls from the `callTool` activity |\n| `cma` | `julep[cma]` | httpx transport for the CMA HTTP adapter |\n| `mcp` | `julep[mcp]` | official MCP SDK, httpx, PyJWT, and cryptography for MCP references, snapshots, calls, and auth |\n| `server` | `julep[server]` | FastAPI/Uvicorn control plane, SSE, Postgres store, Temporal gateway, cryptography, and httpx |\n| `dotctx` | `julep[dotctx]` | rich `.ctx` layout (Jinja2 templates compiled into registered renderers); native `!? $env.get(...)` settings support needs no extra |\n| `providers` | `julep[providers]` | multi-provider `LlmCaller` via any-llm (pair with provider extras) |\n| `otel` | `julep[otel]` | OpenTelemetry span export of the projection |\n| `langfuse` | `julep[langfuse]` | Langfuse OTLP/HTTP export of the projection |\n| `store` | `julep[store]` | artifact distribution stores + bundle signing primitives |\n| `wasm` | `julep[wasm]` | sandboxed wasm execution of bundle-sourced pures (wasmtime host) |\n\n`julep.HAVE_TEMPORAL` reports whether the runtime is available; the package imports and compiles flows either way.\n\n---\n\n## Looking for Julep v1?\n\nJulep v1 (the agents API platform) is preserved on the [`v1` branch](https://github.com/julep-ai/julep/tree/v1) and its docs at v1.docs.julep.ai. Julep 3 is a ground-up rewrite; there is no migration path — v1 and v3 are different products.\n\n---\n\n## License\n\nThis project is licensed under Apache-2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulep-ai%2Fjulep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjulep-ai%2Fjulep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulep-ai%2Fjulep/lists"}