Skip to content

One call to ship

Consider the last minute of a generated app build. The assistant writes files, asks for a check, reads the report, requests a design audit, reads that report, publishes, polls for the alias, and then fetches deployment status. Each call adds latency and another chance for the model to choose the wrong next step. None of those calls improve the app by themselves.

A high-level ship operation can perform that sequence once:

text
save optional files
prepare managed output
run static checks
open the app in a browser
audit the initial design states
classify findings
publish when safe
return the link and compact report

This is not an argument for a single giant tool that edits code by intent. Keep the lower-level file operations from The file tree is an API and the history from Revisions, diffs, and undo. The high-level operation owns the standard delivery sequence after editing.

Concept map

text
precise tools                     high-level tool
ls/read/grep/edit/write/diff  ->  ship
                                 save + prepare + check + publish

The two layers solve different problems. Lower-level tools give the agent control when code needs attention. ship removes orchestration calls when the next steps are fixed.

Terminology

Ship operation means one idempotent request that prepares, checks, and optionally publishes one draft.

Preparation means deterministic generation of managed files or markup before checks.

Hard error means evidence that the initial app cannot safely become live.

Issue means a finding worth reporting that does not stop publication.

Dry ship runs the same path with publication disabled.

Compact result is the model-facing subset of a larger stored report.

Convergence loop is the bounded cycle of ship, fix a concrete issue, and ship again.

Why it saves model turns

Tool calls are not free control flow. Every response enters the transcript. The model must parse it, decide what to do, and often repeat part of the context in the next request. A seven-step release procedure can consume several model iterations even when everything passes.

The service already knows the required order. Static checks must precede browser checks. Managed files must be fresh before either. Publication should use the exact revision that was checked. Encoding this order in prose asks the model to act as a workflow engine. Encoding it in code makes the order deterministic.

The gain is larger than six fewer calls. A single operation can hold internal state without returning intermediate payloads. It can reuse the same normalized files, one browser session, and one classification function. It can persist full reports while returning only:

json
{
  "published": true,
  "url": "https://timer.example.app",
  "revision": 8,
  "hardErrors": [],
  "issues": [
    {"kind": "target-size", "summary": "Reset control is 36px high"}
  ]
}

The model sees the decision and the actionable residue. It does not need screenshots encoded into context, full accessibility trees, provider logs, or every successful assertion.

Production measurements should count assistant turns to the first live link, tool calls per turn, file lines returned, ship attempts, and issues per ship. Without those numbers, a workflow can feel streamlined while still sending hundreds of thousands of cached tokens through repeated reports.

The shape of the operation

A practical input accepts a slug, optional files, optional acceptance flows, optional named visual states, and a publish flag:

ts
type ShipInput = {
  slug: string
  files?: Array<{ path: string; content: string }>
  flows?: Flow[]
  states?: VisualState[]
  publish?: boolean
}

Files should merge by path and pass the same validation as normal writes. If no files are supplied, ship the current draft. This makes follow-up edits efficient: use app_edit, then call ship(\{slug\}).

Preparation must be idempotent. Regenerating a service worker or metadata block should not bump the revision when the bytes are unchanged. Checks should cache by revision, flow fingerprint, and checker version. Publishing an already-live revision should return the existing deployment.

The operation should not throw for ordinary check findings. It should return structured hard errors and issues. Throw only for tool contract failures, missing apps, storage failures, or infrastructure faults that prevent any useful result.

Keep the lower-level tools

It is tempting to replace everything with build_app(\{prompt\}). That hides too much.

When a check reports a syntax error on line 140, the agent needs a range read and a guarded edit. When a user asks to change one label, rewriting the app through a generative operation risks unrelated changes. When a provider fails after files were saved, the next turn needs to inspect the current draft and diff the previous turn.

Lower-level tools also provide escape hatches for new failure classes. A high-level operation can remain stable while file and diagnostic tools grow around it.

The rule is simple: put repeated, deterministic sequencing in the high-level tool. Keep creative and diagnostic decisions in explicit tools.

Decisions and rejected alternatives

One ship call replaces several release calls. Separate finish, audit, and publish tools expose internal phases without giving the model useful control.

Ship may accept files directly. Requiring one write call per file before shipping adds calls for first builds. The same normalizer can save all files atomically.

Ship without files uses the draft. Follow-up work should not resend unchanged source.

Keep a dry mode. The agent or user may want the full pipeline without a public change. A publish: false flag should use the same checks and preparation.

Do not hide edit operations. A one-call release path does not justify one-call code generation.

Return compact findings and store full evidence. Full browser and design reports are useful for UI inspection, not for every model turn.

Bound the convergence loop. Two ships are normal: first publish, then one correction pass. Endless cosmetic polishing is a workflow bug.

Failure modes

Ship publishes a different revision than it checked. Lock the revision through preparation, check, and provider upload, or restart when it changes.

Generated files bump the revision on every call. Make generation byte-stable and compare before writing.

A provider error erases a successful check. Persist check results before publication and return the checked revision.

The model keeps shipping to chase warnings. Rank and cap issues. Guidance should say when to reply with the live link.

A huge result cancels the token savings. Return a short summary and references to stored evidence.

Ship retries create duplicate deployments. Use the operation id and detect an already-live revision.

Dry mode follows different preparation code. Share one pipeline and branch only at publication.

The operation blocks on optional audits. Use bounded concurrency and classify unavailable advisory checks as issues.

Ship-path checklist

  • Can a new app save all initial files in the ship call?
  • Can a follow-up ship the current draft without resending files?
  • Does preparation produce identical bytes on rerun?
  • Are checks cached by revision, flow, and checker version?
  • Is the checked revision the exact published revision?
  • Does a repeated ship of the live revision become a no-op?
  • Are full reports stored while the model sees a compact result?
  • Are contract failures distinct from check findings?
  • Do lower-level read, edit, diff, history, and undo tools remain available?
  • Is there a dry mode using the same pipeline?
  • Are ship attempts and turns to first link measured?
  • Is there a clear stopping rule after publication?

The high-level call removes procedural waste, but it raises a policy question: which findings may stop a publish? Checks that report, not block argues for a narrow hard-error gate and advisory findings everywhere else.

Built from field notes on durable software systems.