Skip to main content

🧊 dagcache

Record your AI agent once. Replay it on every repeat task.

Agents waste time and money re-planning tasks they've already solved. dagcache watches an agent run, remembers the steps it took, and replays those steps the next time a similar task comes in — the LLM only gets called for genuinely new situations.

PyPI Python License: MIT Dependencies

dagcache demo

💡 The idea

Workflow tools like Luigi make you write out the whole workflow up front. dagcache flips that around: it watches your agent work and learns the workflow on its own. Once a way of solving a task proves it works, dagcache saves it and reuses it — like VCR cassettes, but for agent behavior instead of HTTP.

run agent once  ──▶  save the steps it took  ──▶  approve the winning route
                                                        │
        similar task comes in  ──▶  replay the steps (no planning LLM call)
                                                        │
        something changed in the world?  ──▶  fall back to the live agent

🚀 Install

pip install dagcache        # zero runtime dependencies

⚡ Quickstart

import dagcache

@dagcache.tool(pure=True)            # read-only: safe to skip in frozen replay
def search_kb(query: str) -> str: ...

@dagcache.tool(pure=False)           # has side effects: always runs for real
def send_reply(ticket_id: str, body: str) -> str: ...

@dagcache.llm(planning=True)         # decides what to do: skipped on replay
def plan(prompt: str) -> str: ...

@dagcache.llm                        # writes user-facing text: runs fresh
def draft(prompt: str) -> str: ...

@dagcache.agent
def resolve_ticket(ticket: dict) -> str:
    plan(f"handle {ticket['title']}")
    article = search_kb(ticket["title"])
    order = fetch_order(ticket["order_id"])
    send_reply(ticket["id"], f"see {article}")
    return draft(f"article={article}; status={order['status']}")
Run What happens
Run 1 🎬 The agent runs normally. dagcache records the steps it took (search_kb > fetch_order > send_reply, plus the LLM calls) into .dagcache/store.db.
Run 2 🔁 A new ticket of the same shape comes in. dagcache replays the saved steps: tools run again, but with values taken from the new ticket (e.g. its order_id), the planning LLM is never called, and the final reply is written fresh using the new data.
Run 3 🌍 The world changed (a tool returns something unexpected, a value is missing, a tool crashes). Replay stops and the live agent takes over automatically. Worst case, dagcache behaves exactly like your agent does today.

▶️ Try it: python examples/ticket_agent.py

🧠 What gets saved, exactly

dagcache doesn't cache answers — it caches the plan. Two things decide whether a saved plan applies:

  1. which task it is (the @agent function), and
  2. the shape of the inputs (their types and field names — never the actual values).

The saved plan is a graph of steps. Each step's arguments are stored as references, not fixed values:

Reference type What it means at replay
input take it from the new task's input (e.g. ticket.title)
node take it from an earlier step's fresh result (e.g. n1.order_id)
literal a value the LLM made up; reused as-is (with old→new values swapped in)

If the agent finds two different ways to solve the same kind of task, both are kept and ranked by how often they work. dagcache approve locks in the winner. A saved plan that keeps failing is dropped after 3 failures.

🔁 Replay modes

  • verified (default) — tools run for real with fresh values (real side effects, fresh data), planning LLM calls are skipped, text-writing LLM calls run again. Think of it as Luigi's worker, where the learned plan is the workflow.
  • frozen — nothing runs; recorded results are returned as-is. Like Luigi's output().exists() across the whole graph — VCR mode, great for tests and CI.
dagcache.configure(db_path=".dagcache/store.db", replay_mode="frozen")
# or env: DAGCACHE_MODE=record|off, DAGCACHE_REPLAY=verified|frozen, DAGCACHE_DB=...

Two tasks can have the same input shape but mean different things (a refund ticket vs. a complaint). Give the agent a discriminator — its value becomes part of what makes a task unique:

@dagcache.agent(key=lambda ticket: ticket["category"])

🛠 CLI

dagcache ls                    # id, status, recordings, hits, failures, steps
dagcache show 3                # full plan JSON
dagcache approve 3             # lock in the winning route
dagcache demote 3              # back to probation
dagcache diff 3 7              # compare two candidate routes
dagcache export 3 -o c.json    # save a cassette for code review
dagcache import c.json
dagcache prune --status dead --older-than 30

🔌 Framework integrations

OpenAI (duck-typed — works with any .chat.completions.create client):

from dagcache.adapters.openai import wrap_client
client = wrap_client(OpenAI())   # role="auto"|"planning"|"output"

Responses containing tool_calls are treated as planning (skipped on replay); text responses as output writing (run again on replay, with the prompt updated to fresh data).

LangChain (duck-typed — no langchain import required):

from dagcache.adapters.langchain import wrap_tools, wrap_llm
agent = build_agent(llm=wrap_llm(ChatOpenAI(...)), tools=wrap_tools([...]))

⚠️ Honest limitations

  • Matching is exact, not "similar". A saved plan only applies when the task type, input shape, and key all match exactly. Fuzzy/embedding matching is deliberately not in v0.1 — a "close enough" match replaying a path with real side effects is how the cache refunds the wrong customer.
  • Side effects really happen. Verified replay runs effectful tools for real. That's automation, not caching. Mark them pure=False and keep fuzzy matching off.
  • Made-up values can go stale. If the LLM invented a value that came from neither the inputs nor tool results (e.g. a date it guessed), it's replayed verbatim. Change detection only checks the shape of results.
  • Prompt updating is a heuristic: old→new value substitution with word-boundary matching. A value that changed but still looks the same (e.g. a price) is not detected.
  • The agent function must return its final call's result. Custom post-processing after the last LLM/tool call won't run on replay.
  • Cache poisoning is a thing: if agent inputs are attacker-controlled, keep a separate store per principal and approve plans before production.

💎 Ruby

A RubyLLM plugin on the same mental model lives in the companion repo ruby_llm-dagcache — YAML cassettes, DagCache.watch(agent), automatic RubyLLM::Tool instrumentation.

📚 Prior art

📄 License

MIT © dagcache contributors

Metadata

Release files for dagcache 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dagcache 0.1.0
File Size Uploaded
dagcache-0.1.0.tar.gz 32.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dagcache 0.1.0
File Interpreter ABI Platform
dagcache-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 58.2 kB

Release files / dagcache-0.1.0.tar.gz

Download URL dagcache-0.1.0.tar.gz
Size 32.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5365de5e68fd3d45bdd1076fa75a83aca08a65606c8f130eda905e06347dc990
BLAKE2b-256 checksum
How to use checksums
7e62f42d3ebe620325202d0528b7aff4e2397235dee6abb980635449da3230e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / dagcache-0.1.0-py3-none-any.whl

Download URL dagcache-0.1.0-py3-none-any.whl
Size 26.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a8972bc64e5be2fcb0a96485d3f25e5a7495b491e6baa30dba8e86ab3e4e64c8
BLAKE2b-256 checksum
How to use checksums
cbd0af4e2253b0448b30fc75e1f0218f869bb603a45386203dde516ba41bbcd1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page