Skip to main content
This is the complete attribute reference for Agent Template manifests. For the concepts and lifecycle, read Agent Templates; for the authoring journey, Publishing Templates; for the installer’s experience, Deploying Templates.

Where the manifest lives

A template’s manifest exists in three forms. Knowing which form an attribute belongs to tells you where you set it, where it’s stored, and where an installer reads it:
  1. The authored sections — what you write. The template: section of your project’s lua.skill.yaml carries the four authored sections (connections, personaTemplate, triggerPresets, paramsMeta). The publish request can additionally carry sections that have no yaml home yet: the env contract (declared with --env-contract), connection-event triggers (declaredTriggers), outcomes, channels, features, and marketplace-skill composition refs. The publish request also carries three version-level fields: sourceAgentVersion (which promoted agent version to freeze; defaults to the active one), changelog, and referenceEvalRunId (the eval run satisfying the publish gate, when an eval set exists).
  2. The frozen published version — what publish produces. An immutable, integer-numbered version that combines your promoted agent’s primitives (skills, webhooks, jobs, processors, triggers, model — frozen by value) with your authored sections (validated, normalized, and stored alongside), plus derived integrity digests: contentHash, and the codeHash / consentSurfaceHash pair that powers update diffing and re-consent (creator-side version reads only — the two are never in the public manifest).
  3. The served manifest — what installers read. GET /marketplace/templates/:id/versions/:v/manifest serves a safe projection of the frozen version: display metadata and requirements only, never source code, trigger instructions, or values. Fields are lifted by an explicit allow-list — anything not deliberately made public never serves. The serving layer also enriches it at read time: platform display names and auth modes from the live integration catalog, scope display copy, next run times, the per-caller reconciliation block, and the org policy block.
Inheritance vs. clearing. On the publish API, an omitted authored section inherits the previous version’s (re-validated against the new content); an explicit empty value ([], {}, { units: [] } for outcomes, or null for personaTemplate) clears it. The CLI is stricter: when a template: section exists in lua.skill.yaml, all four yaml sections are always sent — an absent or empty subsection is an explicit clear, never “keep the previous version’s”. Omitting --env-contract inherits the previous contract. Keys are identity. Every keyed entry — a skill key, connection key, trigger key, param name, persona var name, outcome unit key — is its stable identity across versions. Renaming a key means remove + add: installers lose their binding, toggle, or setting for the old key. Keep keys stable.

metadata

Template-level, not per-version: set at lua marketplace template create, editable later (except name).
Versions are integers (v1, v2, v3 — not semver), monotonic per template. A rejected publish burns no version number. Catalog categories and icons are not part of the V1 manifest.

agent

agent.personaTemplate

The system prompt as a template with {{VAR}} slots, each declared, typed, and explained. Authored in the yaml template: section. Each entry of vars[]: Substitution rules (what happens to {{vars}} at deploy):
  • Single-pass. The template is scanned once; each declared token is replaced with the installer’s answer. Substituted values are never re-scanned — a value containing {{OTHER_VAR}} lands as inert text, not a new slot.
  • Brace-escaped. Inside supplied values, {{ becomes { { and }} becomes } } — a value can never mint a slot or reach runtime substitution targets.
  • The bijection lint. Every {{TOKEN}} in any branch (base/voice/text) must have a vars[] entry, and every declared var must appear in at least one branch. Publish rejects both directions.
  • Values are never stored in the manifest. Answers land on the installed agent’s persona; the manifest carries only the declarations.

skills[]

Skills (like webhooks, jobs, processors, and code triggers) are frozen from your promoted agent version, by value — you never author these entries. The served manifest lifts display metadata only; source code, tool schemas, and gating conditions never serve publicly. Webhooks and processors serve nothing in the V1 manifest at all — they’re internal wiring, not an installer decision surface. Served shape per entry: Marketplace composition is authored on the publish request as marketplaceSkills: [{ marketplaceSkillId, versionId }] (max 50) — content is still frozen by value into the version; the refs record where it came from and pin the exact version.

connections[]

Declared runtime connection requirements — “this agent needs a CRM” — authored in the yaml template: section. Each entry is a capability satisfiable by any platform in its allow-list. Each entry of platforms[]: In the served manifest, each platform entry is enriched from the live integration catalog at read time — the frozen declaration stores only {type, oauthScopes}:
Raw scope tokens never reach a screen — oauthScopes (and missingScopes in reconciliation) always serve as {scope, displayName, description?} objects with platform-owned display copy. Reconciliation semantics, per attribute. At deploy, the platform reconciles each capability against the connections the installer already holds (personal + the target org’s shared ones): a connection whose type is in platforms[] and whose granted scopes cover the declared oauthScopessatisfied, bound without asking; several matches → multiple-matches, the installer picks (never auto-picked); a match lacking scopes → insufficient-scope with the exact missing scopes; a pre-scope-capture row whose live scope query failed → verify-access (informational, resolved at pre-flight); no match → unmet-required (blocks) or unmet-optional (offered). ownerType filters which grants qualify at all. Install proceeds only when every required capability is satisfied — re-verified server-side, never trusted from the client. The portability lint. A capability may list multiple platforms only when its boundSkills reach the connection through the capability itself. A bound skill that calls platform-native tools (e.g. salesforce_*-prefixed calls, or a platform-typed integration helper) pins the entry: publish rejects with a migration hint — narrow platforms[] to that platform, or migrate the skill to capability-level access. Single-platform entries lint nothing; narrowing is the author’s escape hatch.

triggerPresets and the served triggers[]

Schedules and code triggers are frozen from the source agent; triggerPresets is your authored layer over them — recommended defaults, installer-facing copy, and which schedule fields the installer may edit. It’s a map keyed by trigger key, covering the union of the version’s job, webhook, code-trigger, and connection-event keys. Keys you don’t author get defaults at publish: { enabled: true } for job/webhook/code-trigger keys, always-off for connection-event keys. Each entry of editableParams[]: The whole-minutes constraint. Interval schedules run on whole minutes only: seconds must be ≥ 60 and a multiple of 60, both in the frozen schedule and in any authored envelope — a fractional-minute interval would silently never fire, so publish rejects it. Frozen once schedules are rejected too (a frozen instant is stale by construction), and a frozen schedule must satisfy its own declared envelope. The timezone override. Cron presets carry an IANA timezone — but a frozen cron carries the creator’s timezone, so every cron job automatically gets a preset.timezone editable param (label “Timezone”, defaulting to the installer’s profile timezone) even if you don’t declare one. A Stockholm-authored 08:00 job must not run at Stockholm time for a Nairobi installer.
The served triggers[] merges the frozen schedules, your presets, and the declared connection-event triggers into one list: At install, the deploy request answers this section with triggerOverrides: { <key>: { enabled?, params? } } — e.g. params: { "preset.seconds": 600, "preset.timezone": "Africa/Nairobi" }.

declaredTriggers[]

Connection-event triggers — “when a deal is won” — are authored declarations (publish request only; no yaml home in V1), since no frozen primitive can carry them. Each materializes at install as a managed trigger plus a webhook subscription on the bound connection. The event-support join. Each declared {objectType, event} must be deliverable on every platform in the referenced capability’s platforms[] — checked at publish against the live webhook-event listing, with each filters key limited to that event’s available filters on that platform. A listing fetch failure fails the publish loudly (could not verify event support — try again) — never publish-unverified. Two fences apply everywhere:
  • Never armed by default. A connection-event trigger always serves (and materializes) disabled; authoring enabled: true in its preset is rejected. The installer must switch it on explicitly.
  • event and instruction never serve publicly. The served manifest lifts only key, connectionKey, displayName, description (as a triggers[] entry with type: "connection-event"). Event payloads are untrusted input at runtime and instruction text is consent-surface material — it’s shown through consent flows, not the anonymous catalog.

envContract and params

The declare-vs-supply rule: the manifest declares which environment variables the template’s code expects — names and metadata, never values. Values are supplied per install and stored on the agent. envContract — keyed by variable name, declared with --env-contract "KEY=description" (required) or "KEY?=description" (optional): paramsMeta — the typed display layer over the contract, authored in yaml, keyed by the same names. Keys must be a subset of envContract’s — and supplying a paramsMeta section at all makes coverage total: every contract key must then carry an entry with a displayName (rejection: <KEY>: user-facing params require displayName). Key-derived display names appear only on versions published without any paramsMeta section (API/legacy publishers), so a served manifest never mixes authored and derived entries: The served params[] merges both, one entry per contract key:

channels[] and features[]

Declare-only runtime dependencies — things a deploy can never create but the agent needs to fully go live. Declaring them doesn’t bind anything; it drives the honest post-deploy setup checklist (“connect WhatsApp to go live”) and installer health, instead of a false “live”. Authored on the publish request. channels[] — messaging/voice channels the template sends or receives on: features[] — platform feature flags the agent depends on (e.g. the RAG feature for a docs-answering template):
A WhatsApp declaration deserves a scope note in your description: outbound message-template assets are WABA-scoped and need Meta approval — expect days, not minutes, for outreach beyond the 24-hour window.

outcomes

The template’s countable units of work — “follow-ups sent”, “meetings booked” — declared at publish (publish request only in V1). Counting is platform-attested: a unit is recorded only when the platform itself observes the declared evidence — a matching tool call executed on the install’s bound connection for the named capability, or a platform-recorded event (message delivery, sandbox commerce writes, completed voice calls). Creator code can cause real side effects but cannot mint a count, evidence on an unbound or declined capability is inert rather than counted, and each recorded unit settles when its window passes without reversal — with verification (sampled LLM-judge review before a unit counts as verified) owned entirely by the platform: verification, samplingRate, and judge are rejected as creator fields. outcomes.units[] (max 25 per version): match — tool-call evidence: events[] — platform-recorded event evidence (a closed vocabulary; anything else is rejected at publish): reversals[] — each entry needs at least one evidence source:
Reversals are platform-owned, never installer-toggleable — declining a trigger must not silently disable clawback while units keep settling. And the lifecycle counters (recordedsettledverified / reversed) are served as separate tiers, never summed: only verified may feed pricing or catalog badges.

Served-manifest-only fields

These exist only in the served manifest — derived or attached at read time, never authored:

The reconciliation block

Per capability key: Every install-time rejection (a 409 on the deploy exchange) carries a freshly recomputed reconciliation block — the UI never needs a blind re-fetch.

A complete example

The authored template: section of lua.skill.yaml for a Sales Follow-up agent — all four yaml sections:
The publish-request-only sections for the same template — connection-event triggers, runtime dependencies, and outcomes:
And the served manifest an installer’s deploy screen reads (excerpt):

What the lints check

Publish is the quality gate: every violation is collected and returned in one response with field-path messages, and a rejected publish burns no version number. What you’ll see, by field: