> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Stage agent changes from a coding step

> Load the lua-agent-builder plugin into a Job-tier coding step so it can compile, push and snapshot changes to its own agent, which a person then reviews and releases

After this guide, a Job-tier coding step in one of your [workflows](/concepts/workflows) works on the agent's own Lua project with the `lua` CLI and the **lua-agent-builder** plugin: it restores the source, edits it, compiles, pushes staged versions, backs the source up and creates an [agent version](/concepts/releases-and-versions). It can never make anything live. A person reviews the staged version and promotes it.

*Declaring `plugins` needs the next lua-cli release. A lua-cli that does not know the field drops it without an error, and the step then runs as an ordinary coding turn with no `lua` access.*

**Before you begin**

* A workflow with a Job-tier coding step that runs ([Use the Job tier](/build/workflows/job-tier)).
* A source backup of the agent (`lua push backup`), so the step can restore the project ([Backups and restore](/ship/backups-and-restore)).
* You, or another person, start the run. The step gets its Lua access from that person.

<Steps>
  <Step title="Declare the step">
    Add `plugins: ['lua-agent-builder']` to a Job-tier agent step. `lua-agent-builder` is the only plugin offered today.

    ```ts src/workflows/stage-fix.ts theme={null}
    import { z } from 'zod';
    import { createWorkflow, template } from 'lua-cli';

    export const stageFix = createWorkflow({
      name: 'stage-fix',
      description: 'Restore the agent source, apply a change, and stage it as a new agent version.',
      inputSchema: z.object({ agentId: z.string(), change: z.string() }),
      workspace: { kind: 'empty' },
    })
      .agentStep('stage', {
        agentId: '$self',
        tier: 'job',
        harness: 'claude-code',
        model: 'anthropic/claude-sonnet-4-5',
        plugins: ['lua-agent-builder'],
        workspace: { mount: 'rw' },
        timeoutSeconds: 3600,
        toolScope: { jobTools: ['shell', 'read', 'write', 'edit', 'glob', 'grep', 'git'] },
        prompt: template(
          'Run lua init --ci --agent-id ${initData.agentId} --restore-sources, then make this change: ${initData.change}. ' +
            'Run lua compile --ci, push the changed primitives, run lua push backup --ci --force, and finish with ' +
            'lua version create --ci -m "staged by stage-fix". Report the version number.'
        ),
      })
      .commit();
    ```

    `lua compile` refuses the step with `plugins-invalid` unless all of these hold:

    * the step is `tier: 'job'` and runs on the `claude-code` harness, with an Anthropic model, not `harness: 'generic'`;
    * `shell` is among the step's job tools, and a `'ro'` mount drops it;
    * every plugin is on the platform's list, at most 4, none repeated.

    The platform checks the same rules again when the step starts and fails it, without retrying, if one does not hold.
  </Step>

  <Step title="Start the run as a person">
    The step gets its Lua access from the person who started the run: a short-lived credential that reaches only this agent, can do only what that person can do, and is revoked when the attempt ends. It never enters the step's own container.

    ```bash theme={null}
    lua workflows start stage-fix --input '{"agentId":"<agent-id>","change":"Shorten the greeting in the persona"}' --follow
    ```

    A run started by a schedule, a job, a trigger, another agent or the platform has no person behind it, so the step gets no Lua access: every `lua` command in it fails as forbidden.
  </Step>

  <Step title="Know what the step can do">
    | The step can                                                                                                                | The step can never                                                                                         |
    | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
    | Restore the source (`lua init --restore-sources`, `lua pull`), read every primitive, and run `lua compile` and `lua status` | Promote a version, deploy or publish anything, or activate, deactivate, enable or disable anything         |
    | Push staged versions of skills, jobs and processors, and stage a persona version                                            | Write webhooks or triggers, which are live entry points; it can read them, without their URLs or tokens    |
    | Push, pull and restore source backups                                                                                       | Read or write environment variables                                                                        |
    | List, show, diff and create agent versions                                                                                  | Delete anything, or create or duplicate an agent                                                           |
    | Read the agent's logs                                                                                                       | Write workflow definitions, or start, read or control workflow runs                                        |
    |                                                                                                                             | Chat with the agent, including sandbox chat, or reach its runtime data, products, channels, inbox or voice |

    A refused call answers 403, which lua-cli reports as **forbidden**. `lua push all` still stages what it may: the agent configuration update it also makes is refused, and lua-cli carries on. Secrets such as URLs, tokens and environment values are removed from every response before the step sees it.

    Deploy and promote commands are also blocked by the plugin itself when it runs unattended, so the model cannot talk itself into one.
  </Step>

  <Step title="Review and release">
    When the run finishes, the change is a staged agent version. Nothing the agent serves has changed. Review it, then promote it yourself.

    ```bash theme={null}
    lua version list
    lua version diff <active> <staged>
    lua version promote <staged>
    ```

    Promoting is also the rollback path ([Release an agent to production](/ship/releasing)).
  </Step>
</Steps>

## Options you may need

### Work on another agent

<Warning>
  Not generally available. A plugin step may name `target: { agentId }` beside `plugins` to work on another agent of your organization instead of its own. The platform refuses it (`target_not_granted`) unless the option has been switched on for your environment, which it is not by default.
</Warning>

## If it isn't working

<AccordionGroup>
  <Accordion title="plugins-invalid at compile or push">
    **Cause** The step is not `tier: 'job'`, runs on the `generic` harness, has no `shell` job tool, or names an unknown or repeated plugin. **Fix** Meet every rule listed under the first step of this guide.
  </Accordion>

  <Accordion title="Every lua command in the step fails as forbidden">
    **Cause** Nobody started the run: a schedule, a job, a trigger or another agent did. Or the command is one the step may never run. **Fix** Start the run yourself with `lua workflows start`. For a command in the right-hand column above, run it yourself after the step.
  </Accordion>

  <Accordion title="plugin_unavailable">
    **Cause** The step started on a platform build that does not carry the plugin yet. **Fix** Retry later; the step fails rather than running without its tooling.
  </Accordion>

  <Accordion title="The step ran, but its lua commands cannot reach the agent">
    **Cause** The project was compiled with a lua-cli that does not know `plugins`, which dropped the field, so the step ran as an ordinary coding turn with no Lua access. **Fix** Update lua-cli, compile and push the workflow again.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Use the Job tier" href="/build/workflows/job-tier">Workspaces, coding turns, size classes and limits.</Card>
  <Card title="Claude Code plugin" href="/build-with-ai/claude-code-plugin">What lua-agent-builder does on your own machine.</Card>
  <Card title="Release an agent" href="/ship/releasing">Version, promote and roll back.</Card>
  <Card title="Workflow builder reference" href="/reference/sdk/workflow-builder#agentstep">Every option on `agentStep`.</Card>
</Columns>
