> ## 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.

# Build and release your first agent

> Install lua-cli, create an agent with one weather tool, try it in the sandbox, and release it to production in under ten minutes

After this quickstart, an agent you wrote answers "What's the weather in London?" in production by calling a public weather API. You need Node.js 16 or later. Every command on this page has a non-interactive form, so a coding agent can run it too; signing in takes two commands because the code arrives by email.

*Verified against lua-cli 3.33.0.*

<Steps>
  <Step title="Install the CLI and sign in">
    Install `lua-cli` globally.

    <CodeGroup>
      ```bash npm theme={null}
      npm install -g lua-cli
      ```

      ```bash pnpm theme={null}
      pnpm add -g lua-cli
      ```

      ```bash yarn theme={null}
      yarn global add lua-cli
      ```
    </CodeGroup>

    Sign in with your email: the first command sends you a six-digit code, the second saves a renewable [user session](/concepts/credentials) on this machine.

    ```bash theme={null}
    lua auth configure --email user@example.com
    lua auth configure --email user@example.com --otp <code>
    ```
  </Step>

  <Step title="Create a project">
    `lua init` creates an [agent](/concepts/agents) on the platform and a TypeScript project bound to it. Pass the agent name and an organization ID (`lua agents` lists yours); without flags, `lua init` asks for both.

    ```bash theme={null}
    mkdir docs-quickstart && cd docs-quickstart
    lua init --ci --agent-name docs-quickstart --org-id <org-id>
    ```

    ```text Output theme={null}
    ✅ Creating agent: docs-quickstart
    ✅ Agent created successfully!
    …
    ✅ Lua skill project initialized successfully!
    ```

    You get `src/index.ts` with a `LuaAgent` and a persona template, `lua.skill.yaml` holding the agent ID (the CLI owns this file), and `package.json` with dependencies installed. The [project structure](/get-started/project-structure) page lists the rest.
  </Step>

  <Step title="Add a weather tool">
    A [tool](/concepts/skills-and-tools) is a class with a name, a description, a zod input schema, and an `execute` function. This one geocodes the city, then reads Open-Meteo's current weather; no API key is needed.

    ```ts src/skills/tools/GetWeatherTool.ts theme={null}
    import { LuaTool } from 'lua-cli';
    import { z } from 'zod';

    export default class GetWeatherTool implements LuaTool {
      name = 'get_weather';
      description = 'Get the current temperature and wind speed for a city';

      inputSchema = z.object({
        city: z.string().describe('City name, for example "London"'),
      });

      async execute(input: z.infer<typeof this.inputSchema>) {
        // Open-Meteo needs coordinates, so geocode the city name first.
        const geoUrl = new URL('https://geocoding-api.open-meteo.com/v1/search');
        geoUrl.searchParams.set('name', input.city);
        geoUrl.searchParams.set('count', '1');
        const geo = await fetch(geoUrl).then((res) => res.json());
        const place = geo.results?.[0];
        if (!place) {
          return { error: `No city named "${input.city}" was found` };
        }

        const weatherUrl = new URL('https://api.open-meteo.com/v1/forecast');
        weatherUrl.searchParams.set('latitude', String(place.latitude));
        weatherUrl.searchParams.set('longitude', String(place.longitude));
        weatherUrl.searchParams.set('current_weather', 'true');
        const forecast = await fetch(weatherUrl).then((res) => res.json());

        return {
          city: place.name,
          country: place.country,
          temperatureC: forecast.current_weather.temperature,
          windKmh: forecast.current_weather.windspeed,
          observedAt: forecast.current_weather.time,
        };
      }
    }
    ```

    A skill groups tools and carries the `context` the model reads to decide when to call them.

    ```ts src/skills/weather.skill.ts theme={null}
    import { LuaSkill } from 'lua-cli';
    import GetWeatherTool from './tools/GetWeatherTool';

    export default new LuaSkill({
      name: 'weather',
      description: 'Current weather for any city',
      context: `Use get_weather whenever the user asks about the weather in a city.
    Report the temperature in °C and the wind speed in km/h.
    If the tool returns an error, ask the user to check the city name.`,
      tools: [new GetWeatherTool()],
    });
    ```

    Register the skill on the agent and replace the persona template with one sentence; only what the agent references is compiled.

    ```ts src/index.ts highlight={2,7} theme={null}
    import { LuaAgent } from 'lua-cli';
    import weatherSkill from './skills/weather.skill';

    const agent = new LuaAgent({
      name: 'docs-quickstart',
      persona: 'You are a weather assistant. Answer in one or two sentences.',
      skills: [weatherSkill],
    });
    ```
  </Step>

  <Step title="Test the tool">
    `lua test` runs the tool's `execute` function with the input you pass; no model is involved, so the output is exactly what the tool returns. `--name` is the tool name.

    ```bash theme={null}
    lua test --ci skill --name get_weather --input '{"city":"London"}'
    ```

    ```text Output theme={null}
    ✅ Selected tool: get_weather
    🚀 Executing tool...
    ✅ Tool execution successful!

    Tool returned: Object — fields: city, country, temperatureC, windKmh, observedAt
    Output:
    {
      city: 'London',
      country: 'United Kingdom',
      temperatureC: 22.1,
      windKmh: 18.4,
      observedAt: '2026-09-12T17:15'
    }
    ```
  </Step>

  <Step title="Chat in the sandbox">
    `lua chat -e sandbox` compiles your code and uploads it as [sandbox](/concepts/environments) versions the platform runs, so the model can call your tool without a release. The first run after you add a skill registers it on the server and may answer without it (`Skipping skill weather - no skillId found in lua.skill.yaml`); run the command again. `-t` starts a fresh thread so earlier test conversations don't leak into this one.

    ```bash theme={null}
    lua chat --ci -e sandbox -m "What's the weather in London?" -t
    ```

    ```text Output theme={null}
    💡 Sandbox mode: uses your locally compiled code — no lua push needed.
    …
    ✅ Pushed 1 skills to sandbox
    ℹ️  Thread: 9f6d6f57-4f7a-43b4-9524-4eed15971fc7
    …
    It's currently 22.1°C in London with wind speeds of 18.4 km/h.
    ```
  </Step>

  <Step title="Release it">
    `lua push all` uploads a version of the skill, which changes nothing for end users, and of the [persona](/concepts/persona), which is served at once; `lua version create` snapshots the agent; `lua version promote` makes that snapshot live. `promote` accepts `1` or `v1`.

    ```bash theme={null}
    lua push all --ci --force
    ```

    ```text Output theme={null}
    📦 Pushing 1 skill(s)...
      📝 weather: 1.0.0 → 1.0.1
      ✅ weather v1.0.1 pushed

    🌙 Pushing persona...
      ✅ Persona version 1 created
    …
    ✅ Push All Complete!
    ```

    ```bash theme={null}
    lua version create --ci -m "quickstart"
    lua version promote 1
    ```

    ```text Output theme={null}
    ✓ Created v1 (staged). Run `lua version promote v1` to deploy.
    ✓ Promoted v1. (No previous active version.)
    ```

    <Warning>
      Promoting takes effect for every end user at once. To go back, promote the previous version number; see [Release an agent to production](/ship/releasing).
    </Warning>
  </Step>
</Steps>

<Check>
  Ask the production agent the same question. It should answer with the current temperature, which shows the promoted version is live and calling your tool.

  ```bash theme={null}
  lua chat --ci -e production -m "What's the weather in London?" -t
  ```

  ```text Output theme={null}
  ℹ️  Thread: f9ca5667-ed65-4a33-8721-7ddb153a2bbb
  …
  It's currently 22.1°C in London with wind speeds of 18.4 km/h — a mild early-September evening.
  ```
</Check>

## Next steps

<Columns cols={3}>
  <Card title="Build a support agent" href="/get-started/build-an-agent">
    Continue this project: two data-backed tools, a persona, knowledge, a webhook, a job, and the web widget.
  </Card>

  <Card title="Connect a channel" href="/channels/overview">
    Put the agent on the web widget, WhatsApp, Slack, email, or a phone number.
  </Card>

  <Card title="Add knowledge" href="/build/add-knowledge">
    Upload documents and turn on Knowledge Search, with no release needed.
  </Card>
</Columns>

<Columns cols={2}>
  <Card title="About agents" href="/concepts/agents">
    What an agent contains and how the compiler turns your project into one.
  </Card>

  <Card title="CLI reference" href="/reference/cli/overview">
    Every command, flag, exit code, and where credentials come from.
  </Card>
</Columns>
