Agents

The agent workflow

How agents draft, validate, and preview content in Myna while humans keep the publish gate.

Every write in Content lands as a draft on a change set, every revision records who or what made it, one preview URL covers the whole change set, and publishing is a separate, explicitly scoped operation. An agent can work for hours and nothing is public until a human — or a deliberately publish-scoped principal — publishes.

For the Feedback product's agent loop, see reports and boards.

What the workflow guarantees

Property Mechanism
Reviewable units of work A change set groups entry and asset operations into one atomic, previewable batch
Attribution Every revision and asset records created_by_type (user, api_key, agent, system) and created_by_id
Safe by default Writes create draft revisions; nothing is published as a side effect
One link to review A single preview URL renders the entire change set as it would look published
Human gate Publishing requires the content:publish scope, which agent keys should not have
Concurrency safety expectedRevisionId gives optimistic concurrency across concurrent agents

Scoping agent keys

Create a dedicated API key for each agent with these scopes:

content:read, content:write, assets:read, assets:write, preview:write, schema:read

Deliberately omit content:publish. The agent can draft, validate, and preview everything, but a publish attempt fails with PERMISSION_DENIED (HTTP 403) — the agent stops, reports the change set id and preview URL, and a human publishes after review.

Add feedback:read and feedback:write if the agent also works the report queue. Keys are myna_sk_... bearer tokens.

The canonical loop

  1. Open a change set — a titled container for the whole task.
  2. Create and update entries, upload assets — attach every write to the change set and include a changeSummary explaining each change.
  3. Validate the change set — every changed resource is checked against the deployed schemas.
  4. Create one preview URL — a single link covering everything in the change set.
  5. Human reviews — field-level diffs in the dashboard, rendered output via the preview link.
  6. Human publishes — atomically, in one transaction. Or a publish-scoped principal does, on explicit instruction.

Via MCP

With the MCP server configured, the loop is a fixed tool sequence (full inputs in the tool reference):

{ "tool": "myna_create_change_set", "arguments": { "title": "Q3 launch posts" } }

Then, referencing the returned chs_ id on every write:

{ "tool": "myna_create_entry", "arguments": { "collection": "posts", "data": { "title": "Hello" }, "changeSet": "chs_...", "changeSummary": "Draft launch announcement" } }
{ "tool": "myna_update_entry", "arguments": { "entry": "posts/hello", "data": { "body": "..." }, "expectedRevisionId": "rev_...", "changeSet": "chs_...", "changeSummary": "Tighten intro" } }
{ "tool": "myna_upload_asset", "arguments": { "filename": "cover.png", "path": "./cover.png" } }
{ "tool": "myna_validate_change_set", "arguments": { "changeSet": "chs_..." } }
{ "tool": "myna_create_preview", "arguments": { "changeSet": "chs_..." } }

The agent reports the preview URL and stops. Destructive tools additionally require confirm: true, and myna_publish_change_set fails without content:publish regardless.

Via the SDK

The same loop with the management client:

import { createManagementClient } from "@myna-sh/sdk/management";

const myna = createManagementClient({ token: process.env.MYNA_TOKEN! });
const project = "my-site";

const cs = await myna.changeSets.create(project, { title: "Q3 launch posts" });

const entry = await myna.entries.create(project, {
  collection: "posts",
  data: { title: "Hello", body: "..." },
  changeSetId: cs.id,
  changeSummary: "Draft launch announcement",
});

await myna.entries.update(project, entry.id, {
  data: { body: "Revised body" },
  expectedRevisionId: entry.draftRevisionId ?? undefined,
  changeSetId: cs.id,
  changeSummary: "Tighten intro",
});

const asset = await myna.assets.upload(project, "./cover.png");

const validation = await myna.changeSets.validate(project, cs.id);
if (!validation.valid) throw new Error(JSON.stringify(validation.errors));

const preview = await myna.previews.create(project, { changeSetId: cs.id });
console.log(preview.url); // hand this to the human

Mutations automatically carry an Idempotency-Key, so retried calls are replay-safe.

Via the CLI

The CLI mirrors the same steps (MYNA_TOKEN or myna login):

$ myna changes create --title "Q3 launch posts"
$ myna entries create posts --set title="Hello" --change-set chs_... --summary "Draft launch announcement"
$ myna entries update posts/hello --set body="Revised" --expected-revision rev_... --change-set chs_... --summary "Tighten intro"
$ myna assets upload ./cover.png
$ myna changes validate chs_...
$ myna changes preview chs_...
# Human reviews, then:
$ myna changes publish chs_... --confirm-publish

myna changes validate exits non-zero when the change set is invalid, and publish refuses to run without the explicit --confirm-publish flag.

Attribution

The field-level diff shows agent-authored revisions distinctly from human edits, annotated with their changeSummary. Include one on every write, so the review reads like a commit log.

When an unauthorized publish is attempted

Publishing is a single transaction: it applies every item in the change set or none. A principal without content:publish — via myna_publish_change_set, changeSets.publish(), or myna changes publish — gets PERMISSION_DENIED (HTTP 403) and nothing is partially applied.

Concurrent agents and optimistic concurrency

When multiple agents (or an agent and a human) may touch the same entry, pass expectedRevisionId on updates. If another writer created a newer revision in the meantime, the update fails with STALE_REVISION (HTTP 409) instead of silently clobbering it. Recover by re-reading the entry, merging against the latest data, and retrying with the new revision id. Revisions are immutable, so nothing is ever lost — myna_get_entry_revisions lists the full history and myna_restore_revision can restore any prior state into a new draft.