Skip to content
Jetrepo
Esc
↑↓navigate↵open⌘Jpreview
On this page

Agent execution workflow

Discover live schemas, propose exact changes, follow Approval, and verify durable results.

Start here when operating Jetrepo from an external agent. Management MCP is /agent/mcp on the installation’s Management origin. It exposes exactly inspect, propose, execute, and control_operation; domain actions are versioned Operation Definitions returned by inspection.

These docs explain the workflow. The connected installation’s tool schemas, Operation Definitions, IDs, permissions, and state are authoritative for each call. Never invent a tool, definition version, permission, identifier, or field configuration from an example.

1. Establish identity and scope

Use OAuth or delegated Agent Auth for authoring. Organization-owned Agent keys are limited to allowlisted operational permissions and cannot create or delete model or content resources. Model-provider credentials stay in the agent host.

Begin with an explicit inspection kind:

{ "kind": "ORGANIZATIONS" }

Select the user’s intended Organization. Inspect BACKENDS for an existing Backend, then ENVIRONMENTS with that stable Backend ID. Match names to returned IDs; do not create duplicates because an earlier conversation omitted an ID.

Normal calls carry backend and environment together. Organization bootstrap uses Organization scope without either field. Stable Backend, logical Environment, and physical generation IDs are different values; use each where the returned schema requires it.

2. Bootstrap only if needed

For an empty Organization where canCreateBackend is true, inspect OPERATION_DEFINITIONS with organization set to its ID, names: ["backend.create"], and detail: "FULL" for its live version and input schema.

An Organization Change Set has version: 1, base { organizationId, perspective: "ORGANIZATION" }, and a single backend.create operation with a localRef. It has no fabricated manifest, Backend, or Environment. Propose it without top-level resource scope, then execute the returned Candidate ID/digest with an idempotency key.

Wait for the Operation and use the returned stable Backend, main Environment, and initial Draft manifest for subsequent work. Bootstrap requires backend.create and operation.execute; it is autonomous and does not publish Content.

3. Discover the exact change contract

Request known exact Operation Definition names together with detail: "FULL"; their current versions and schemas come from the live response. When names are unknown, inspect the compact OPERATION_DEFINITIONS catalogue first. Its default detail: "SUMMARY" retains names, versions, descriptions, identity classes, requester/approver permissions, risk, and Approval mode. Full definitions include schema/resource URIs, concurrency, queue, and recovery details. Summary catalogue pages accept limit: 250; full pages and other lists accept at most 100. The default remains 50. Follow nextCursor. names accepts 1–25 exact names and must remain unchanged while following that query’s cursor. Unknown or unauthorized names return no entry.

Each full definition includes inputSchema for its operation’s arguments and outputSchema for the domain result recorded after execution. These are separate from the enclosing Candidate and Operation responses. Retain discovered schemas for this task instead of fetching the full catalogue again for every write. Acquire the exact Draft base through SETTINGS with backend.read, or reuse a completed Operation’s paired result.draftManifestId and result.draftManifestHash in the same scope. Refresh after drift; do not guess a manifest or hash.

SETTINGS.publicationPolicy.automaticApprovals reports the operator’s Backend flag. When true, permitted Backend-scoped actions may execute without human Approval even if their definition normally requires it. Candidate policy inputs and the resulting Operation state are authoritative. The flag grants no permissions and cannot be enabled by an agent; cross-Backend or Organization-wide credential requests retain normal policy. A flag change can invalidate older Candidates.

Inspect MODEL with model.read for the selected Backend and Environment before authoring. It returns language IDs and the default locale, plus paginated Data Types and Components with their IDs, Field Set IDs, Field IDs, types, translation settings, and configurations. Follow nextCursor; Data Type, resource, and Field scopes filter the model. These domain IDs do not replace canonical resource selectors required for existing-resource changes.

Read the Change Set schema through the advertised resource templates when supported. Use the returned definition schema for required values, defaults, identity classes, permissions, policy, quotas, and recovery. Models, Content, Assets, locales, Taxonomy, Releases, webhooks, and Preview each have their own definitions.

Existing-resource selectors require resourceKind, resourceId, and expectedRevisionId. Creation declares localRef; dependencies use a local selector or { "$localRef": "name" }. Do not submit internal $localId values.

A Data Type and its Field Set have different IDs. Use the actual Field Set ID when creating Fields. Within a Change Set, a $localRef in the fieldSetId argument resolves to the created Data Type or Component’s Field Set ID; other identifier positions resolve to the created resource’s domain ID. No arbitrary output-property reference syntax exists. Batch dependency-compatible operations using $localRef; separate batches only when a later input needs a returned value that the live reference schema cannot express, or a quota or Approval boundary requires it.

For example, include both operations below in the same Change Set’s operations array. Use the discovered Backend ID for backendId and retain the exact Change Set base. Fetch the full live definitions before adapting this example:

[
  {
    kind: 'data_type.create',
    version: 1,
    localRef: 'article',
    arguments: {
      backendId,
      name: 'Article',
      key: 'article',
      flavor: 'CONTENT',
      translationMode: 'FIELD',
    },
  },
  {
    kind: 'field.create',
    version: 1,
    localRef: 'title',
    arguments: {
      backendId,
      fieldSetId: { $localRef: 'article' },
      name: 'Title',
      key: 'title',
      isTranslatable: true,
      isHiddenInAdmin: false,
      isHiddenFromApi: false,
      type: 'ShortText',
      config: {
        setting: { isTitle: true },
        appearance: { placeholder: 'Title', displayCharCount: false },
        validation: {
          required: { value: true, errorMessage: 'Title is required.' },
        },
      },
    },
  },
];

The same Field Set reference works with component.create. Version 2 Content writes also resolve new owners, Fields, Components and locales in the same Change Set. Version 1 action path strings cannot interpolate local references.

Field configuration and values

field.create and field.configuration.validate expose a configuration schema for each Field type. Read that schema before constructing config; a Short Text Field, for example, uses config.setting.multiple, while a Component Field uses config.setting.allowedComponents and config.setting.hasMultipleValues. These settings are not interchangeable. Configuration validation returns { "valid": true } and has no resource projection.

Write Content by Field key

When the live catalogue includes entity.create_with_fields@2, entity.update@2 and asset.import_url@2, prefer their content-shaped inputs. values maps Field keys to scalar values or ordered lists in the default locale. locales maps explicit locale codes to Field-key values. Data Type and Component references accept a domain UUID, { "key": "page" }, or a Change Set local reference.

For example, these are arguments for entity.create_with_fields@2, inside an exact Change Set with a creation localRef:

{
  backendId,
  dataType: { key: 'page' },
  values: {
    slug: 'about',
    sections: [
      {
        $component: { key: 'text_section' },
        values: { heading: 'About us', body: 'Our story.' },
      },
    ],
  },
  locales: { en: { title: 'About' }, ka: { title: 'ჩვენ შესახებ' } },
}

Use the model’s actual keys and configurations. Component objects use $component, optional values, and optional locales; child values inherit the enclosing locale. Content, Asset and Taxonomy references use domain UUIDs or { "$localRef": "name" } directly as values.

Omitted Fields stay unchanged on update and absent on create. A supplied list replaces the whole list in that locale, preserving order; omitted children of replaced Components are removed with the old instances. { "$unset": true } removes a value. Empty strings and lists differ from removal. Numbers and booleans must retain their JSON types; null is supported only by JSON Fields. { "$json": value } stores one literal JSON value in a JSON Field, including an array or null. No locale fallback values are copied: on create, a translatable Field omitted in a locale has no value there and Delivery falls back to the default locale, so write an explicit empty value when that locale must be empty. The proposal reports such omissions as a non-blocking LOCALE_FALLBACK warning in validation.issues. Non-translatable Fields require the default locale, and a Field/locale cannot appear in both values and locales.

For a migration from structured records, inspect MODEL_INFERENCE takes up to 500 sample records as { group, file, data, body }, where a <locale>/<path> file is the translation of <path>, and returns a proposable model Change Set against the current Draft base, a mapping from source keys to Fields, and notes on the decisions the data could not make. Types are groups of records with near-identical keys, and records of one group whose keys are a subset of another’s are the same type with optional keys. A group that is one type is named after the group, singular when it holds several records, and camelCase source keys become snake_case keys while the mapping keeps the source key. Arrays of same-shaped objects become shared Components, small closed value sets become Taxonomies, locale-suffixed twins such as name and name_en become one translatable Field, and a Field whose value differs between locale counterparts is translatable. A lang key declares a document’s locale and is not a Field. The notes state what was detected: how many primary documents are the default locale, how many <locale>/ documents paired with them, which locales the Change Set creates, and any locale-folder document without a primary. Locale twins and translatability are decided per type; when one source key is a translatable twin, or translatable, in some types and a plain Field in others, a note names both sets of types and the mapping edit that makes them agree, because whether the key means the same thing in every type is not in the data. The Backend’s default locale is never changed: primary documents that declare another language are that locale’s translation, the default locale’s values come from its own <default>/ documents, and a key a translation document omits is read from the primary document, except a translatable value: the default locale’s own document decides it, so a value only the primary carries is written empty there rather than copied from another language. A title still falls back to the primary, and locales.onMissing: fallback copies the primary’s value. When the Backend already has a model, inference maps the records onto it first: a type or Component that shares at least half of its Field keys with a live owner is that owner, its Fields match by key, live types and translatability win, a locale twin whose own source key names a live Field becomes that Field, and vocabularies match by key or term set. Each type, Field, Component and vocabulary in the mapping then carries existing with its live id or is new, the Change Set proposes only what is new, and bindings lists the Assets, by source path, and Terms the Backend already holds. Propose the Change Set as returned. To change a decision, edit the mapping and inspect MODEL_INFERENCE again with the same records and the edited mapping as mapping: the Change Set is then built from that mapping instead of inferred, so an edit such as making a key translatable is made once and the import reads the same decision. Then entity.import_records@1 takes the mapping, the records, and assets and terms bindings by UUID or $localRef, and expands during proposal into one entity.create_with_fields@2 per record with localRef <import localRef>.<file stem>, so the Candidate seals exactly those keyed writes. Each type’s identity Field, its slug or title, makes the import re-runnable: a record whose identity value already exists in that type expands into entity.update@2 with the exact selector and expectedVersion instead of a create, and the Candidate summary’s resourceChanges counts creates against updates. Translatable Fields absent in a locale receive an explicit empty value unless the mapping sets locales.onMissing to fallback.

Updates supply entityId and expectedVersion with an exact resource selector; they omit dataType. Import arguments supply url, optional filename, dataType, and optional values/locales. Initial Asset metadata requires content.create and commits with the Asset, avoiding a later content.update operation. Data Type, Field, resource and locale scopes still apply. Authoring requires a delegated human; Management execution requires operation.execute.

Keys resolve against the exact Draft base and newly created model resources before Candidate hashing. The sealed Candidate contains native Field actions; execution does not resolve names again. New owners, Fields and nested Content may share one Candidate. Commit edits or deletions to existing model resources before proposing Content by key. Each write is limited to 10000 compiled Field actions and Component depth 5, alongside normal installation quotas.

Precise edits with version 1 actions

Version 1 Content actions address Field IDs, not Field keys. language is the Backend’s language ID, not a locale key or a manifest resource ID. Primitive values are arrays even for a single value. Non-translatable Fields require the default language ID.

To create a Component list, emit these actions in order, using IDs from the selected model:

  1. upsert the root Field ID with value: [] to initialize the list.
  2. add at fieldId.0 with value: [componentId] to append its first item.
  3. upsert at fieldId.0.childFieldId with the child value array.
  4. Repeat for later items using their zero-based indices. Append to an existing list at its current length; do not initialize an existing list again.

Delivery objects containing _component are read representations, not write payloads. A nonempty Component initialization is rejected. For translated lists, initialize each language separately; for a shared list, initialize it in the default language and write translatable child Fields in each requested language. Both translations are retained.

4. Propose and inspect

Call propose with the same scope and a version-1 Change Set bound to the exact base. Proposing persists an immutable Candidate and performs no managed-content mutation.

The response contains row, document, and uri. By default, document is a summary: digest, scope, validation, impact, requester, policy, expiry, operation count/kinds, and change counts. operationMap maps each canonical outcome itemKey to its submitted index, localRef, and definition name; retain it because dependencies can reorder execution. Use row.id as candidateId and copy document.digest unchanged into execute.digest. Proposing again can create another Candidate, so inspect existing work after a lost response before submitting another proposal. The default Candidate lifetime is 900 seconds; expiresInSeconds accepts 60–3600.

A Change Set may carry an optional defaults block holding a uniform kind, version, and arguments shared by every operation, so a repeated value such as backendId or a single dataType is stated once rather than per operation. An operation’s own kind, version, or individual argument keys override the default. Defaults expand before validation, so the resulting Candidate, digest, impact, and policy are exactly those of the fully written-out form; the compression never changes what is reviewed or executed. An operation whose kind or version is absent from both itself and defaults is rejected.

Large uniform batches are better generated from the source data than authored row by row, then submitted through these same governed tools. Generating the Draft is ordinary source-data handling; it is not a substitute mutation path, and authorization, policy, quotas, and Approval continue to decide the outcome.

Read validation, affected-resource counts, scope, requester, policy, expiry, and digest. To review normalized arguments, exact proposed values, individual diffs, or retained historical names, request detail: "FULL" on the original proposal or on inspect CANDIDATE with its ID. Full responses return the sealed document once, outside row; summary presentation never changes that document or its digest. Resolve failures by inspecting the current schema/state and proposing a corrected Candidate. Do not execute an invalid or expired proposal.

Argument validation errors return invalid_input with issues containing path, code, and message. Paths such as changeSet.operations.0.arguments.config.setting.multiple refer to the submitted operation index, even when dependencies change execution order. Correct those inputs before proposing again; a failed validation creates no Candidate.

Use retained review evidence when available. Missing historical names or before-values are unknown; never substitute current mutable values and present them as historical facts.

5. Execute once and follow the Operation

Call execute with the same Backend/Environment, exact candidateId, exact digest, and a caller-chosen idempotencyKey. Organization bootstrap omits Backend/Environment. Reuse the same key after a transport failure for that same execution; a different Candidate requires a different key.

Set waitMs: 10000 when you also have operation.read and want completion evidence in this call. The optional integer accepts 0–10000 milliseconds and defaults to 0 (immediate response). Positive waits return the Operation row plus outcomes, outcomeSummary, and nextOutcomeCursor; they retain committed Draft metadata in result when available. The first page contains up to 250 summary outcomes, or 100 full outcomes with detail: "FULL". Full waited responses put domain bodies in outcomes, without repeating them in result.outputs for Backend work.

Execution still runs durably. Check the returned status: a timeout returns current progress and any recorded outcomes; AWAITING_APPROVAL and terminal states return promptly. Continue observation with control_operation, action: "STATUS", the same operationId and scope, and another bounded waitMs. Positive waits are invalid on other control actions. A disconnected request stops observation, not execution. Read authority is checked before starting waited execution and refreshed before returning evidence.

When outcome evidence is needed without waiting, use inspect OPERATION directly; it already includes status. A preceding STATUS poll is unnecessary. For longer work, leave a modest delay between status calls instead of repeatedly fetching full output bodies. A queued response is not completion.

State Agent action
AWAITING_APPROVAL Return Candidate/review and Operation links. Wait for an authorized human to decide in Jetrepo.
QUEUED, RUNNING, CANCELLING, RECOVERING Observe the existing Operation; do not submit a duplicate Change Set.
SUCCEEDED, RECOVERED Read the recorded outcome and resulting state before reporting success.
FAILED Inspect failures and recovery evidence. Retry only when explicitly retryable and within the user’s authorized work. Invalid input, a missing resource, or a denied permission is never retryable.
CANCELLED, EXPIRED, SUPERSEDED Report the state and inspect the reason before proposing replacement work.

APPROVE and REJECT require a human principal, operation.approve, and the exact approver permission. Chat consent or a client’s tool confirmation does not substitute for Approval. Distinct policy excludes requester and delegating human from approving.

6. Verify and hand back context

Use outcome evidence already returned by a waited call, or inspect OPERATION with its ID. Summary inspection pages accept up to 250 outcomes; full pages accept up to 100, with a default of 50. limit and cursor paginate item outcomes; pass a waited response’s nextOutcomeCursor as the next inspection’s cursor and continue until it is null. outcomeSummary describes the complete Operation. Do not report every item successful from the first page or collapse repeated steps because their names match.

Summary status responses omit repeated output bodies while retaining outputCount. Summary outcomes retain each itemKey, status, failure evidence, useful domain identifiers such as id, typeId, and fieldSetId, and the domain result’s version when present. Use that result version for an edit’s expectedVersion, together with its committed selector; subsequent changes still require fresh preconditions. An imported Asset’s file includes available id, size, mimeType, extension, and originalName, so file-size and identity verification do not require a full result. Null summary values and duplicate _id aliases are omitted when id already carries the same value. Failure details remain intact.

Projected outcomes also return selector when their committed revision is available in the Operation’s resulting manifest. Use this exact resource kind, canonical ID, and expectedRevisionId for later proposals; an intervening change still triggers normal drift checks. An absent selector is not permission to invent one. Bind created IDs with the retained operationMap; reread MODEL when current model/configuration is needed rather than solely to rediscover IDs already returned. Use detail: "FULL" for complete domain output bodies, then the public Delivery surface to verify the consumer result.

Return readable names, resulting state, failures, and exact Candidate/Operation links supplied by the client or installation. Keep IDs for follow-up. A Draft result is not Published; publication needs an explicit request and policy check.

After a lost response, inspect existing work before retrying. When base, revision, policy, or authority changes, read current state and propose again. Do not overwrite drift or bypass guards through direct database/API writes.

7. Choose the next surface

  • Publication: discover the lifecycle definition, follow the user’s requested scope, and use the same Candidate/Approval/Operation sequence.
  • Frontend: inspect DELIVERY, read exact OpenAPI bytes/hash, and follow contract generation. REST relationships remain IDs and Components remain inline.
  • Agent retrieval: use Context MCP with a Delivery key or Preview Bearer. Management credentials do not authorize Delivery.
  • Rendered Candidate: use Preview handoffs, a Verified target, and the same requesting credential for the one-time claim.
  • Runtime key: request the governed Delivery-key flow. Human Approval is required by default; the operator’s Backend automatic-approval flag may waive it for a key restricted to that Backend. Claim and store the secret once with the requesting credential. Keep it out of code, logs, and summaries.
  • Publication recovery: use eligible rollback controls. Rollback creates a new Candidate over current Published history; it is not a generic Draft undo.

Documentation for agents

Read /llms.txt for the prioritized index. Append .md to a guide URL for plain Markdown; the home is /index.md. /llms-full.txt contains the complete public corpus. The Management origin also answers /llms.txt: it states that installation’s Management MCP and discovery endpoints and, when the operator configured the documentation origin, embeds this index with absolute links; otherwise it points at the live schemas. These static reading aids are not a Management endpoint or credentials.

The optional Jetrepo agent skill packages this general workflow for an agent host, with a script that submits bulk Change Sets and keeps evidence in files. The documentation origin publishes it at /.well-known/agent-skills/, so the skills CLI installs the instructions and the script into Claude Code, Codex, Cursor, and other hosts:

npx skills add https://docs.example.com

Replace the host with your documentation origin. Add -g to install it for your user instead of the current project. The skill includes no credentials, site-specific migration runner, or alternative write API: its script calls the same four Management tools, and live schemas and the user’s authorized scope remain authoritative.

Was this page helpful?