Skip to main content
A draft holds the unpublished edits one user is making on one branch of one agent. Drafts are scoped to the requester — two users can edit the same branch concurrently without colliding, and each user only ever sees their own draft. Publishing a draft promotes it to a new immutable version on the branch and clears the draft row. A draft owns:
  • A config — the same JSON shape stored in agent_version.config. In this release that’s just { system_prompt }.
  • An updated_at timestamp set by the most recent PUT.
There is no “draft id.” Drafts are addressed by (agent_id, branch_id, user_id).

Lifecycle

PUT is idempotent and upserts the current user’s draft on the branch. DELETE discards it. POST .../publish runs the draft under a row-level FOR UPDATE lock on the branch — autosaves that race with publish either land before the lock and are published, or land after and survive, but never get silently dropped.

Publishing

POST /v1/agents/{agentId}/branches/{branchId}/publish:
  1. Reads the draft inside the transaction (under the branch lock).
  2. Returns 400 no_draft if the requester has no draft on this branch.
  3. Allocates the next version_number for the branch under the same lock — concurrent publishes serialize behind it instead of racing the unique (branch_id, version_number) index.
  4. Inserts a new agent_version, advances the branch’s head_version_id to it, and deletes the draft row.
  5. Returns the new version’s full detail (config + audit fields).
The new version’s parent_version_id is the branch’s head_version_id as observed under the lock, so a publish that races a merge into the same branch still chains correctly.

Authorization

Same rules as Branches: organization-scoped, 404 no_active_organization when the session has no active org. Drafts themselves are user-scoped within that org — there is no cross-user read of another user’s draft.