Skip to main content
After this guide, the model calls your tools when it should, with the arguments you need, and not otherwise. A skill’s context is injected into the prompt whenever the skill is active, and each tool’s description is what the model reads to pick a tool: both are prompt text. Use this guide when a tool is ignored, called with missing arguments, or called when it shouldn’t be; for the agent’s overall tone or identity, change the persona instead. Verified against lua-cli 3.33.0. Before you begin
1

Write the rules that span the tools

context carries what applies across the skill: when to ask before calling, what never to do, and what to say back. Name tools by their name; the model matches on it. The skill’s own description is listing text for lua skills view and the admin dashboard, not prompt text.
Don't
Do
The first version leaves the model to infer everything from tool names, so it guesses emails and escalates on the first complaint. The second says what each tool is for and what must be true before it runs.
2

Describe each tool for the model

description says what one tool does and when it applies, in one or two sentences. Field descriptions in inputSchema tell the model how to fill each argument, and constraints such as .email() or an enum reject values it made up.
Don't
Do
Rules that span tools belong in context; what one tool does belongs in its description.
3

Test on a fresh thread

lua chat compiles the project and runs the conversation with your local skills, so a context change needs no push (About environments). -t starts a fresh thread each time; without it the previous answer shapes the next one.
With the rewritten context, the first message ends in a question for your email rather than a ticket, and the second calls lookup_tickets without asking. The first sandbox run after adding a skill registers it and answers without the tools (Skipping skill tickets - no skillId found in lua.skill.yaml); run it again.
4

Verify

Read the skill’s log: --name is the skill’s name, and each entry names the tool the model called and the arguments it chose.
Output
A Calling tool with input {…} line shows the arguments; if one is invented, describe that field in inputSchema and say in context to ask for it.
5

Release

lua push uploads a version and changes nothing for end users; lua version create snapshots the agent; lua version promote <n> makes that snapshot live and is also the rollback path (Release an agent to production).
lua version create prints ✓ Created v<n> (staged). Run `lua version promote v<n>` to deploy.; <n> comes from that line, promote accepts <n> or v<n> and asks no confirmation, and in a script n=$(lua version list --limit 1 --json --ci | jq -r '.[0].version') reads it.

Options you may need

Give voice and text different context

context also takes { base, voice, text }: base applies everywhere and voice or text is added for that kind of conversation; at least one must be set. The persona uses the same object form.

Hide the skill when it doesn’t apply

A skill condition runs before every turn with a 30-second budget, so keep it to one cheap read. When it returns false or throws, the tools and the context are left out of the prompt: the end user talks to an agent that has never heard of the skill. Your file differs; add only the highlighted lines to your own LuaSkill.
src/skills/tickets.skill.ts
accountId is a field your own tools stored on the end user’s record (Identify users). To gate one tool instead, put the condition on the tool class: the skill’s context stays in the prompt, so the model can still say what it can’t do for this user.

Teach a formatting component

A ::: formatting block is a prompt rule like any other. Put the instruction in the skill’s context when the component belongs to that skill’s replies (a list-item per product in a catalog skill, a payment block after create_checkout), and in the persona when it should apply to every reply. Blocks render differently per channel, so name the channel when a block renders only there: “On WhatsApp, send the verification flow.”

If it isn’t working

Either the skill’s condition returned false or threw, which hides the skill, or the context never says when the tool applies. Check what User.get() returns for the end user you test with, then name the tool in context.
The field has no .describe() and nothing in context says to ask for it. Add both, and add a constraint (.email(), .regex(), an enum) so a wrong value is rejected before execute runs.
Without -t both messages share your default thread, so the first answer anchors the second. Run each probe with -t, or lua chat clear between them.

Next steps

Skills and tools

How context and descriptions reach the model, and when to split skills.

Test an agent before you release

Local runs, sandbox chat, thread isolation.

LuaSkill reference

Every field, the context object form, and errors.