{"slug": "databricks-lakebase-give-your-agent-a-branch-not-your-production-database", "title": "Databricks Lakebase: Give Your Agent a Branch, Not Your Production Database", "summary": "Databricks Lakebase, a Postgres-compatible database with decoupled storage on lake storage, enables sub-second branching and ephemeral instances, making it suitable for agentic data projects that need isolated, production-like environments. The system uses Paxos-based safekeepers and page servers to achieve low-latency reads and writes, with branches costing about 500 milliseconds to create and idle branches scaling to zero. Databricks reports 12 million database launches per day, indicating that ephemeral instances are the intended usage pattern.", "body_md": "There's an awkward moment in every agentic data project. The agent works. It writes decent SQL, it reasons about schema, it proposes a migration that looks right. And then somebody asks the question nobody wants to answer: *what happens when it's wrong against production?*\n\nThe usual answers are all bad.\n\nPoint it at production with read-only credentials, and you've capped the damage but also capped the agent — it can diagnose and never fix. Point it at a seeded staging database, and you get an agent that's confidently correct about data that stopped resembling production four months ago. Spin up a container per run, and you're maintaining fixture pipelines forever while paying for compute that sits idle between runs.\n\nWhat you actually want is a full copy of production that appears in under a second, costs almost nothing while it exists, and vanishes without a trace when the agent is done. Which is to say: you want `git checkout -b`\n\nfor your database.\n\nDatabricks Lakebase does this, and the mechanism is interesting enough to be worth understanding rather than just enabling.\n\nLakebase is Postgres — genuinely Postgres, not a wire-compatible reimplementation. What Databricks changed is the part underneath.\n\nStandard Postgres is a monolith with compute and storage tightly coupled. Databricks decoupled the two and moved storage onto lake storage, which creates an immediate problem: object storage has high latency and no transactional consistency, and Postgres assumes neither. Two components patch the gap. **Safekeepers**, built on the Paxos consensus algorithm, handle low-latency durable writes. **Page servers** handle low-latency reads, materializing pages on demand from the lake.\n\nOnce storage is decoupled and page-addressed, branching becomes almost free. It's copy-on-write: the data stays in one place on the lake, and Lakebase tracks only the deltas between branches. Writing to a branch records the change independently without touching the others.\n\nThe numbers that matter for agent work: a branch takes roughly **500 milliseconds**, a new instance comes up in under 500 milliseconds, rollback to an earlier snapshot is equally fast, and idle branches scale to zero so you pay only cheap lake storage while they sit around. Databricks reports 12 million database launches per day in production, which is less a benchmark than an indication that ephemeral instances are the intended usage pattern rather than an edge case.\n\nThat last property is what makes this viable for agents specifically. As Ali Ghodsi put it on the summit keynote stage, agents \"don't want to wait 10 minutes on a database to come up.\" An agent that has to wait ten minutes for an environment will simply be given production instead, because the alternative makes the loop unusable. Sub-second provisioning is what makes the safe option also the convenient one.\n\n| Approach | Provisioning time | Data realism | Idle cost | Blast radius of a bad write | Ongoing maintenance |\n|---|---|---|---|---|---|\n| Read-only prod credentials | None | Perfect | None | Zero — but agent can't act | None |\n| Full prod write access | None | Perfect | None | Catastrophic | None |\n| Seeded staging database | Minutes to hours | Degrades continuously | Full instance cost | Contained, but shared between runs | High — fixture pipelines, refresh jobs |\n| Container per run with dump restore | Minutes, scales with data size | Good at restore time | None between runs | Contained per run | Medium — dump/restore tooling |\n| Lakebase branch per run | ~500 ms | Identical to parent at branch time | Near zero (storage deltas only) | Contained per run, discardable | Low |\n\nThe column that usually decides it is provisioning time, because it governs whether developers actually use the isolated path or route around it under deadline pressure.\n\nHere's the pattern end to end. The Databricks-specific surface is deliberately isolated into one thin layer; everything else is ordinary Postgres, because that's what it is.\n\n**Step one — get an instance.** Lakebase is now included in Databricks Free Edition alongside Genie Code, serverless GPUs, Agent Bricks and LakeFlow Designer, so this costs nothing to follow along with.\n\n```\n# lakebase_control.py — the only Databricks-specific layer in this design.\n# Endpoint shapes for branch operations are still settling; check current\n# docs before shipping. Everything downstream of get_dsn() is plain Postgres.\n\nimport os\nimport time\nimport requests\n\nWORKSPACE = os.environ[\"DATABRICKS_HOST\"].rstrip(\"/\")\nTOKEN = os.environ[\"DATABRICKS_TOKEN\"]\nHEADERS = {\"Authorization\": f\"Bearer {TOKEN}\"}\n\ndef create_branch(parent: str, name: str) -> dict:\n    \"\"\"Branch an existing Lakebase instance. Copy-on-write; returns in ~500ms.\"\"\"\n    started = time.perf_counter()\n    resp = requests.post(\n        f\"{WORKSPACE}/api/2.0/database/instances/{parent}/branches\",\n        headers=HEADERS,\n        json={\"name\": name},\n        timeout=30,\n    )\n    resp.raise_for_status()\n    elapsed_ms = (time.perf_counter() - started) * 1000\n    print(f\"branch {name!r} ready in {elapsed_ms:.0f} ms\")\n    return resp.json()\n\ndef delete_branch(parent: str, name: str) -> None:\n    requests.delete(\n        f\"{WORKSPACE}/api/2.0/database/instances/{parent}/branches/{name}\",\n        headers=HEADERS,\n        timeout=30,\n    ).raise_for_status()\n\ndef get_dsn(instance: str, branch: str) -> str:\n    \"\"\"Standard Postgres connection string for a branch.\"\"\"\n    resp = requests.get(\n        f\"{WORKSPACE}/api/2.0/database/instances/{instance}/branches/{branch}\",\n        headers=HEADERS,\n        timeout=30,\n    )\n    resp.raise_for_status()\n    host = resp.json()[\"read_write_dns\"]\n    return f\"postgresql://{os.environ['LAKEBASE_USER']}:{TOKEN}@{host}:5432/databricks_postgres?sslmode=require\"\n```\n\n**Step two — wrap it so the agent physically cannot see production.** This is the important part, and it's boring on purpose. The agent never receives a DSN; it receives a session bound to a branch.\n\n``` python\n# agent_sandbox.py\nimport contextlib\nimport psycopg\nfrom lakebase_control import create_branch, delete_branch, get_dsn\n\nPROD_INSTANCE = \"orders-prod\"\n\n@contextlib.contextmanager\ndef branched_session(run_id: str, keep_on_failure: bool = True):\n    \"\"\"\n    Yield a Postgres connection on a throwaway branch of production.\n\n    On success the branch is discarded. On failure it is retained so you can\n    attach a psql session and see exactly what the agent did — which is the\n    single most useful debugging affordance in this whole design.\n    \"\"\"\n    branch = f\"agent-{run_id}\"\n    create_branch(PROD_INSTANCE, branch)\n    conn = None\n    try:\n        conn = psycopg.connect(get_dsn(PROD_INSTANCE, branch), autocommit=False)\n        yield conn\n        conn.commit()\n        delete_branch(PROD_INSTANCE, branch)\n    except Exception:\n        if conn:\n            conn.rollback()\n        if not keep_on_failure:\n            delete_branch(PROD_INSTANCE, branch)\n        else:\n            print(f\"retained branch {branch!r} for inspection\")\n        raise\n    finally:\n        if conn:\n            conn.close()\n```\n\n**Step three — verify before you believe anything.** An agent reporting success is not evidence. Snapshot the invariants you care about before and after, and diff them.\n\n``` python\n# verify.py\nfrom dataclasses import dataclass\n\nINVARIANTS = {\n    \"order_count\":        \"SELECT count(*) FROM orders\",\n    \"open_order_total\":   \"SELECT coalesce(sum(total_cents), 0) FROM orders WHERE status = 'open'\",\n    \"orphaned_items\":     \"SELECT count(*) FROM order_items i \"\n                          \"LEFT JOIN orders o ON o.id = i.order_id WHERE o.id IS NULL\",\n    \"negative_totals\":    \"SELECT count(*) FROM orders WHERE total_cents < 0\",\n    \"duplicate_skus\":     \"SELECT count(*) FROM (\"\n                          \"  SELECT sku FROM products GROUP BY sku HAVING count(*) > 1\"\n                          \") d\",\n}\n\n@dataclass\nclass Snapshot:\n    values: dict\n\n    @classmethod\n    def take(cls, conn) -> \"Snapshot\":\n        out = {}\n        with conn.cursor() as cur:\n            for name, sql in INVARIANTS.items():\n                cur.execute(sql)\n                out[name] = cur.fetchone()[0]\n        return cls(out)\n\n    def diff(self, other: \"Snapshot\") -> dict:\n        return {\n            k: (self.values[k], other.values[k])\n            for k in self.values\n            if self.values[k] != other.values[k]\n        }\n\ndef assert_no_corruption(before: Snapshot, after: Snapshot) -> None:\n    \"\"\"Hard invariants must not move regardless of what the task was.\"\"\"\n    for guard in (\"orphaned_items\", \"negative_totals\", \"duplicate_skus\"):\n        if after.values[guard] != before.values[guard]:\n            raise AssertionError(\n                f\"invariant {guard} violated: \"\n                f\"{before.values[guard]} -> {after.values[guard]}\"\n            )\n```\n\n**Step four — the run itself.**\n\n``` python\n# run_agent_task.py\nimport uuid\nfrom agent_sandbox import branched_session\nfrom verify import Snapshot, assert_no_corruption\n\ndef run(task: str, agent) -> dict:\n    run_id = uuid.uuid4().hex[:8]\n\n    with branched_session(run_id) as conn:\n        before = Snapshot.take(conn)\n\n        # The agent gets a live connection to real-shaped data and full write\n        # access — to a branch that exists only for this run.\n        agent.execute(task, conn)\n\n        after = Snapshot.take(conn)\n        assert_no_corruption(before, after)\n\n        return {\n            \"run_id\": run_id,\n            \"changed\": after.diff(before),\n        }\n```\n\nThis isn't a pattern I invented for the article — it's the shape Databricks uses internally for Genie ZeroOps, its autonomous pipeline-remediation agent. ZeroOps investigates a failure, drafts a fix, then creates shallow clones of production data using the same branching mechanism as Lakebase, deploys the proposed fix to the clone, verifies row counts, and presents the result as a pull request. Nothing reaches production without explicit human approval. The verification-on-a-clone step is what makes autonomous remediation defensible rather than reckless, and it's available to you as a primitive.\n\nWorth being direct about, because the isolation is narrower than it feels.\n\n**Side effects escape the branch.** Your database is isolated. The payment API your agent calls is not. Neither is the email it sends, the Slack message it posts, or the Kafka topic it publishes to. A branch protects state you own in Postgres and nothing else — every external integration needs its own sandboxing, and this is where teams get burned after assuming the branch covered them.\n\n**Branch sprawl is real.** Sub-second creation plus retain-on-failure produces a lot of branches. Enforce TTLs from day one, tag branches with the run that created them, and put a reaper on a schedule. Copy-on-write storage is cheap, not free, and a branch that diverges heavily from its parent stops being cheap.\n\n**Fresh at branch time, stale immediately after.** A long-running agent task is reasoning about a snapshot. If it takes twenty minutes and the underlying question was time-sensitive, the conclusion may be wrong by the time it lands — not because the isolation failed but because you asked a real-time question of a point-in-time copy.\n\n**Invariant checks only catch what you thought to write down.** The `assert_no_corruption`\n\nfunction above is a floor, not a ceiling. It catches structural damage. It won't catch an agent that correctly updated the wrong customer's record, because that's semantically valid and structurally clean. Human review of the diff remains load-bearing.\n\nThe reason this matters beyond Databricks is that it closes a gap that has quietly constrained every agentic data project: agents were either safe or useful, and picking both required infrastructure most teams weren't going to build.\n\nApplication code got this right decades ago. Nobody tests a refactor by editing production source — you branch, you break things freely, you merge what works. Databases never got that affordance because copying one was expensive enough that the ergonomics never arrived. Decoupling compute from storage is what finally made the copy cheap, and cheap copies are what make experimentation safe.\n\nGive the agent a branch. Let it break things. Read the diff.\n\n*Lakebase branch APIs are evolving; the control-plane calls above are structurally accurate but verify endpoint and field names against current Databricks documentation before running in anger. Everything downstream of the DSN is standard Postgres and will behave exactly as you expect.*", "url": "https://wpnews.pro/news/databricks-lakebase-give-your-agent-a-branch-not-your-production-database", "canonical_source": "https://dev.to/jubinsoni/databricks-lakebase-give-your-agent-a-branch-not-your-production-database-3l29", "published_at": "2026-08-02 19:49:56+00:00", "updated_at": "2026-08-02 20:17:26.318743+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Databricks", "Ali Ghodsi", "Lakebase", "Postgres", "Paxos"], "alternates": {"html": "https://wpnews.pro/news/databricks-lakebase-give-your-agent-a-branch-not-your-production-database", "markdown": "https://wpnews.pro/news/databricks-lakebase-give-your-agent-a-branch-not-your-production-database.md", "text": "https://wpnews.pro/news/databricks-lakebase-give-your-agent-a-branch-not-your-production-database.txt", "jsonld": "https://wpnews.pro/news/databricks-lakebase-give-your-agent-a-branch-not-your-production-database.jsonld"}}