onInstall and onUninstall sections of the template manifest: what the installed agent does right after an install and right before an uninstall. installPolicy limits how many installs one organization may hold. All three are authored by hand; lua marketplace template draft never composes them.
Verified against lua-cli 3.33.0.
Shape
Both hooks take the same object with at least one field; any other field, or a section that is not an object, is refused.{{var}} tokens are refused in a hook; nothing is substituted.
string
Bare name of a tool in this version’s frozen skills, executed directly with input
{} and no model involved. A name that does not resolve against the frozen skills is refused at publish.string
Plain text, 1 to 2000 characters after trimming, sent as one background agent turn. Served on the manifest so the installer reads it before consenting.
{ workflow: string, input?: object }
onInstall only. Starts one run of a workflow frozen in this version, as the installer. input is at most 8 KB and is validated against the workflow’s input schema.lua.skill.yaml
onInstall timing
After a successful install or apply, on the first install and on every apply whose tool name or instruction differs from the last one that ran.- The tool runs first, as the installer, with a 180-second budget. The instruction then fires as one background turn on channel
template-install, prefixed[Install: <displayName>]; when the tool failed, the turn is told with[Setup step "<tool>" failed: <error>]. - The install never waits on either half and never fails because of one. The response returns as soon as both are claimed; failures are recorded on the install.
- Idempotent per install: the tool is keyed by name and the turn by a hash of the instruction, so a re-run of the same version, a retry, or an update with an unchanged hook does not run it again. A changed tool name or instruction runs once more.
- A fleet
applyor platform provisioning has no installer, so the hook runs in system scope. A one-call deploy defers both halves until the deployer’s access to the deployed agent is registered; the install response then saysonInstallDeferred: true.
onUninstall timing
At the start of an uninstall, before any primitive, env key, subscription, or connection binding is removed, so the tool still has the skills,LUA_TRIGGER_URL__* values, and connections it needs.
- The tool is awaited: teardown waits for it, within its budget.
- The instruction fires on channel
template-uninstall, prefixed[Uninstall: <displayName>], and is awaited for at most 30 seconds; teardown then proceeds regardless. Cleanup that must happen belongs intool. - Both run as the removing user. With no acting user (an internal removal), the tool is skipped and the turn runs in system scope.
- The outcome
{ ran, ok?, error? }is recorded on the install and returned asonUninstallon the uninstall response. Cleanup never fails the uninstall; the CLI prints✅ Template uninstalled from this agent.either way.
Install result fields
lua marketplace template install --json prints these beside the fields on the agent templates API.
boolean
true when this install claimed and fired the instruction turn; absent otherwise.{ toolRan: boolean, ok?: boolean }
toolRan is true when the tool was claimed. It runs in the background; read the result back as setup.boolean
true on a one-call deploy: the hooks fire after the deployer grant, so the outcomes come from that later call.string
The run
startWorkflow started, or the run an earlier same-version install already started.{ code, message }
Why
startWorkflow started no run: workflow-not-materialized, control-plane-unavailable, control-plane-dark, start-refused, or start-failed. The install itself succeeded.Setup status and retry
GET /agents/:agentId/templates reports setup for each installed template that declares a tool or startWorkflow: { status: 'running' | 'ok' | 'failed', error?, code?, workflowRunId? }. failed wins over running, which wins over ok. A tool marker with no result after about 220 seconds reports Setup step did not finish; a deferred hook whose fire never arrived reports Setup step never started.
POST /agents/:agentId/templates/:templateId/setup/retry re-runs the tool when its last run failed or died, re-attempts startWorkflow, and never re-fires the instruction turn. Its answer, whose startWorkflow.outcome is started, already-started, failed, or not-declared, is on the agent templates API. No CLI action calls either route.
installPolicy
'single' | 'multiple'
required
single allows one installed row of the template per organization. multiple, the behavior when the section is absent, sets no limit.lua.skill.yaml
single, a second install or deploy into an organization that already holds an installed row is refused with HTTP 409 SINGLE_INSTALL_PER_WORKSPACE before anything is created; the response names the agent that holds it. The holding agent may re-install or upgrade; an apply_failed row holds no slot; an uninstall frees it; an agent outside any organization is exempt. Installers see This template can only be installed once per workspace, and this workspace already has it. Use the agent that has it, or uninstall it there first.
draft drops the section, so re-add it after every draft. On the publish API an omitted installPolicy inherits the previous version’s and null clears it; from the CLI, removing the section clears it and the consequence prompt prints installPolicy: CLEARING the section — new installs are no longer limited to one per workspace.
Lints
See also
- Template manifest — the other sections and the served fields
- Lints and consent — every code and what installers consent to
- Install and apply — where the hooks fire from
LuaTool— writing the tool a hook namesenv— readingLUA_TRIGGER_URL__*inside a setup tool

