Dibbla Docs Get started Guides Workflows Changelog
Open Console
Guides

Deploy from an agent

Let a connected AI agent publish an app in one tool call — from the files in the call itself, from a source archive it uploads, or from a commit in the repository your app is linked to — change a single file in an app that is already running, how the source travels, how you follow the build, and what your organization's deployment governance does to it.

An agent that can write your app but not publish it leaves you in a terminal for the last step. The platform connector closes that: with the deploy scope, a connected agent starts the deployment in one tool call and follows the build to a URL that answers.

Before that call, the agent can check its work: validate the manifest and preview what the deploy would apply with the read-scoped preflight tools, and after the deploy lands, run the app’s checks. See Check before you deploy for the whole flow.

There are three ways to give that call its source, and which one fits depends on where the agent runs — not on what you are deploying. All three end in the same deployment, with the same governance, the same limits and the same operation to follow.

This guide is about what that call does — and, just as importantly, what it does not do. It never approves anything.

What you need

  • The platform connector connected and verified.
  • A grant that includes the deploy scope. It is not part of the read defaults, so you approve it deliberately: request scopes = ["platform:deployments:execute"] in the client’s MCP configuration, start a new authorization, and approve it on the Dibbla consent page.
  • Source that is ready to deploy: a Dockerfile at its root, or a dibbla.yaml manifest. The same rules as dibbla deploy apply, because it is the same deployment path.
  • For the repository source below: the app connected to a GitHub repository in the Console. That connection is made by an organization admin, once, and the agent cannot make or change it.

Three sources, one deployment

Your agent runsGive the callWhy
In a chat with no shell at all — ChatGPT, or any hosted assistantfilesIt wrote the code in the conversation; there is no disk to pack
Anywhere, when the app already runs and one file changesfiles with mode: "patch"Only what changed travels; the rest of the app is left alone
On your machine or anywhere it can reach the internet freelyarchiveThe source is on a disk it can read, and it can upload
In a cloud sandbox with restricted network accesssource (repo and ref)It can already push to GitHub; it does not need to reach us with bytes

Send exactly one of the three. Sending two, or none, is refused with a sentence saying so rather than a guess.

Deploying the files themselves: no filesystem at all

A hosted assistant has no terminal, no disk and nothing to upload. It has the files it just wrote. So it sends them: files is a list of { file, data, encoding } with mode: "replace", and the tool call is the whole deployment.

{
  "alias": "my-app",
  "mode": "replace",
  "files": [
    {
      "file": "Dockerfile",
      "data": "FROM nginx:alpine\nCOPY index.html /usr/share/nginx/html/\n"
    },
    { "file": "index.html", "data": "<!doctype html><h1>Hello</h1>" }
  ],
  "idempotency_key": "1a2b3c4d-…"
}

encoding is utf-8 when you leave it out, which is what source text is; base64 is there for the occasional binary asset. Paths are relative to the source directory — Dockerfile, app/main.go — never absolute and never climbing out of it.

There is nothing to upload afterwards: the answer is an operation id, and the build is already on its way.

You do not write any of that yourself. You ask:

Build me a tiny web page that says “Hello from Dibbla”, and publish it to my Dibbla app my-app. Deploy it by sending the files in the call — you have no shell here.

This path is for source, not for archives

The inline tree is capped at 200 files and 2 MB of decoded content. That is generous for source — every template Dibbla ships is a few hundred kilobytes of it — and deliberately far too small for a build. Over the cap, the call is refused with a sentence naming the limit and pointing you at the archive path or dibbla deploy. Build output and dependencies do not belong in a deployment at all: exclude them, do not pack them into a tool call.

A cloud agent should not have to open your network

A cloud coding session typically reaches package registries and GitHub, and nothing else — while its MCP traffic goes through its provider’s servers and always arrives here. So an upload URL on our origin is unreachable from that sandbox unless you add our domains to its allowed list. Deploying from a commit removes that problem instead of working around it: the agent pushes a branch, names it, and the platform fetches the source itself.

Deploying an archive: one call, not a sequence

The agent calls platform_deployment_start once, with the application alias and the identity of the archive it has on disk — the exact byte size and the SHA-256 of the tar.gz. The call returns immediately with:

  • an operation id (deployment:…) that outlives the conversation, and
  • a structured upload plan: where the client process sends the archive bytes, and in what size chunks.

The client then pushes the archive itself. The deployment starts on its own when the last chunk lands. Nothing about the build happens inside the tool call, and the archive never passes through the model — it is a byte stream on a separate, principal-bound endpoint, not a tool argument.

Never paste an archive into a prompt

No field anywhere in the connector takes an archive’s bytes. A build archive in a model’s context window is expensive, lossy and unnecessary; the upload plan exists so the client process moves the bytes and the model only reads the outcome. The inline files path above is a different thing — source the agent wrote in this turn, small and bounded — and it is never where a tar.gz goes.

Resuming, and never deploying twice

Every call carries an idempotency key you choose once per intended deployment. Calling again with the same key returns the same operation and the same upload intent, so an upload that was interrupted resumes from the committed offset. It does not create a second deployment. The same key with a different archive is refused as a conflict rather than silently adopted.

Deploying a commit: nothing to upload

When the agent has pushed its work, it calls the same tool with the repository and a ref instead of an archive — the branch it just pushed, a tag, or a commit SHA. No bytes move from the agent at all.

Two things happen inside that call:

  • The ref is pinned to a commit. The answer names the exact commit_sha being deployed, so a branch that moves afterwards — someone else’s push, or the agent’s own next commit — does not change what this deployment publishes.
  • The source is fetched by Dibbla, with Dibbla’s own credential for the connected repository. A private repository works exactly like a public one, and no token is ever handed to the agent, written into the operation, or visible to the model.

An app may only deploy from the repository it is connected to. A ref that does not exist, a repository the app is not connected to, and a repository in another organization all answer the same way — the deployment is refused as “no such repository and ref for this application”, because telling those cases apart would say which repositories exist.

The rest is identical to the archive path: the same idempotency key rule (the same key returns the same operation, so a retry is not a second deployment), the same operation to follow, the same governance, the same limits.

Changing one file in a running app

Publishing an app and fixing a line in one are different jobs. Sending the whole tree again to change one file is expensive, and it quietly overwrites whatever anyone else deployed while the agent was working.

So files has a second mode. With mode: "patch", the agent sends only the files that change — each as its whole new content — and delete for paths that should go. Everything it does not send stays exactly as it is, down to the file mode, and the deployment is one commit on top of the revision your app is running.

{
  "alias": "my-app",
  "mode": "patch",
  "files": [{ "file": "index.html", "data": "<!doctype html><h1>Hej</h1>" }],
  "delete": ["old-banner.html"],
  "base_sha": "9f1c0b7a4d2e6f8091a3b5c7d9e1f3a5b7c9d1e3",
  "idempotency_key": "5e6f7a8b-…"
}

The loop the agent runs is read → change → patch, and none of it needs a terminal, a download or the Console:

  1. platform_files (action: read) returns the file as it is running, and the revision it was read at.
  2. The agent changes the file in its own context.
  3. platform_deployment_start with mode: "patch", that file, and that revision as base_sha.

You ask for it in one sentence:

In my Dibbla app my-app, the heading on the start page says “Hello” — change it to “Hej” and deploy it. Patch just that file; don’t re-send the whole app.

base_sha is a lock you can choose to hold

base_sha is the revision the agent read the files at. If the app has been deployed since — by a colleague, by CI, by another agent — the patch is refused, and the refusal names the revision that is current now so the agent can read the file again and re-send. Nothing is merged and nothing is guessed.

It is optional. Left out, the patch applies to whatever revision is current at the moment it arrives, which is what you want when you are the only one deploying.

Whole files, never diffs

A patch sends the complete new content of each file it changes. There is no line-based diff, no hunk header and no context lines anywhere in the connector — a model that has to count lines gets them wrong, and matching a diff that does not quite fit is a failure we would rather not have. Files are the unit; a file that is 400 lines long travels as 400 lines.

A patch is not a rollback

Going back to an earlier version is not a separate tool. The history is already there: platform_deployments lists one commit per deploy, and platform_files reads any of them with a ref. Read the old content at the revision you want and patch it back.

A delete path that is not in the revision being patched is refused rather than ignored — an agent that believes your app holds a file it does not should find that out before it deploys, not after.

Starting from the app that is already running

Both sources above assume the agent already has the code. Often it does not: you ask a connected agent to change something in an app that has been running for months, and it has never seen that source. dibbla clone is how a human answers that — and an agent has no terminal to run it in.

If the change is small, you do not need this section at all: read the file with platform_files and send it back with mode: "patch", as Changing one file in a running app describes. What follows is for the case where the agent wants the whole app on a filesystem.

If the agent only needs to see the code — to answer a question, or to find the file it is about to change — it does not download anything at all: platform_files finds, searches and reads the deployed source directly, and Connect an AI agent describes those three actions. What follows is for the case where the whole app has to land on a filesystem, because the agent will edit a checkout and deploy it.

So the same source is a download. With the file-read scope, the agent asks for the application rather than for a file it once uploaded:

  1. platform_files with action: prepare_download and the app’s alias (and optionally a ref to start from a particular deploy rather than the latest one — a revision from platform_deployments, or the one a finished deploy reported as the revision it built). The answer is a principal-bound intent that declares the archive’s exact content_length and content_sha256.
  2. The client process fetches the bytes itself from the intent’s download field, with the same access token it uses for MCP, and verifies them against that content_sha256. An interrupted fetch continues with a Range: bytes=<the last byte it actually kept>-; the client’s own count is what a resume is based on, not the server’s, and it never starts over. A client with no filesystem hands the console link to you instead, exactly as an upload does.
  3. The agent unpacks the .tar.gz, changes what you asked it to change, and deploys the result through the archive path above.

That is the whole loop — clone, change, deploy — with no terminal in it. The download is the same transfer plane as the upload, so the same rules hold: no bytes and no base64 pass through the model, the link is not a presigned storage URL and carries no credential of its own, and an app in another organization or one you cannot reach is simply absent rather than refused.

The archive is what Dibbla deployed, not your working tree

The source comes from the Dibbla-managed repository written by your deploys, so it is the code that is running — not any uncommitted work on your laptop and not a fork on GitHub. An app that has never been deployed with version control enabled has no archive to hand out.

Following the build

The deployment is a durable operation, so it is followed with the same tools as every other long-running thing on the connector:

QuestionTool
What phase is it in?platform_operation (view: status, the default)
What is the build doing?platform_operation (view: logs)
What happened, step by step?platform_operation (view: events)
What is the app’s URL?platform_operation (view: output)
Stop it before it reaches the clusterplatform_operation_cancel

Build logs are paged and bounded — an agent reads a window of them, not a whole BuildKit transcript. When the deployment succeeds, the output carries the application’s URL. When it fails, the output carries the same build error dibbla deploy would have printed: the failing step, the parsed compiler diagnostics and a bounded log tail. A missing Dockerfile, an invalid manifest or a port that is not a port are reported as what to fix, not as “the deploy failed”.

Cancel is not undo

A deployment can be cancelled while it is queued or still preparing. Once it has begun changing the cluster it is past the point of cancellation, and the connector reports a conflict rather than rewriting what already happened.

The deploy lands — unless your organization asks for review

By default a deploy from an agent goes straight out. The one call rolls the app out and the output carries its URL, for a brand-new app and for a change to one that is already running. Nothing waits for a person.

Review is something an organization turns on for itself, and when it is on the agent does not get to choose and cannot work around it.

Effective modelWhat the one call does
immediate (the default)The deployment rolls out. The output carries the app’s URL.
admin_four_eyes, member_four_eyes, admin_checkpointA pending deployment is created. Nothing is live until a person approves.

Turning approval on

An owner or admin opens Console → Organization settings → Deployments, picks a review policy and saves. It applies to every app in the organization from that moment; a single app can be given its own policy on the app’s own settings. Switching back to immediate rollout is the same two clicks.

Two of the review policies need someone other than the author to approve, so in a one-person organization they cannot be satisfied — the Console says so next to them before you pick one.

If you used to see pending deployments and no longer do

Organizations that required admin four-eyes without ever having chosen it were moved to immediate rollout in September 2026: the requirement was written for them when the setting was introduced, not picked by anyone. The organizations affected were notified, and the Deployments settings page says so. Nothing was removed — pick a review policy there and approvals are back.

When the deployment came from a repository, the pending deployment in the Console names the repository, the ref and the commit SHA alongside the exact diff. The reviewer decides about a commit they can go and read, rather than about an opaque candidate.

When review is required, the operation’s output says so explicitly: it names the effective model, the pending deployment, and the Console link where an authorized person decides. The connector has no approve, deny, apply, close or retry tool — that is not an omission to be fixed later, it is the boundary. And the rule underneath is not enforced by the connector at all: Dibbla refuses to let the author of a deployment be the person who approves it, whatever role the author holds. Asking the agent to approve its own work therefore fails the same way it would fail for you in the Console.

A brand-new application — an alias that does not exist yet — is created directly. Governance describes how changes to an existing app are released.

The limits that still apply

Everything that bounds dibbla deploy bounds this, in the same words:

  • Plan and trial limits. An organization at its app limit, or past the end of its trial, is refused before a single byte is uploaded, with the same error the CLI prints.
  • Your role. A grant is a delegation, not a promotion. If your role in the organization cannot deploy, neither can your agent.
  • Source size. The archive is capped at 50 MB, and a repository is held to the same cap once packed. Exclude build output and dependencies with a .dibblaignore. An inline files tree is capped much lower — 200 files and 2 MB decoded — because it travels through the model. A patch obeys the same cap on what it sends; the app it patches may be any size.
  • Integrity and tenancy. The upload is verified against the SHA-256 you declared; an archive that does not match is refused and nothing is deployed. An archive belonging to another organization resolves to nothing.
  • Resource defaults. A deployment that does not name cpu or memory gets your organization’s configured defaults, exactly as the CLI does.

Troubleshooting

What you seeWhat it meansWhat to do
The deploy tool is missing from the tool listThe grant does not include the deploy scopeReconnect and approve the deploy scope explicitly
The operation stays queued and never buildsThe archive has not finished uploadingRead the operation’s status; it carries the offset to resume the upload from
IDEMPOTENCY_KEY_CONFLICTThat key already names a different deploymentReuse a key only to resume the identical deploy; pick a new one otherwise
The output says a pending deployment was createdThis app requires reviewOpen the Console link and have an authorized colleague decide
The archive was refused for its digestThe uploaded bytes are not the ones that were declaredRecompute the SHA-256 of the exact tar.gz and start a new deploy
The build failed with a compiler errorThe build is the same build the CLI runsRead the parsed diagnostics in the operation’s output and fix the source
SOURCE_NOT_FOUND for a repo and refThe ref does not exist, or the app is not connected to that repositoryPush the branch, check the spelling, and check the app’s repository connection in the Console
The inline files were refused for the limitThe tree is over 200 files or 2 MB decodedDeploy the same source as an archive, or with dibbla deploy from a machine that has it
BASE_SHA_STALE on a patchThe app was deployed after the agent read the filesRead the files again at the revision the refusal names, and send the patch based on that one
A patch says there is no source to patchThe app has never been deployed with version control, or the alias is not yoursDeploy the complete tree once with mode: "replace"; patches apply to what a previous deploy stored
SOURCE_UNAVAILABLEThis Dibbla installation could not reach the repositoryNothing was deployed and nothing in your source is wrong; retry, and tell us if it persists