User.get() is the current end user inside a tool; in a webhook, trigger, or job nobody is talking, so you pass an id (execution contexts). For records that aren’t about one person, use Data.
Verified against lua-cli 3.33.0.
Before you begin
- A skill on the agent to hold the tools (Add a tool to a skill).
- An idea of what you’ll store per person: an account id, preferences, or progress through a flow.
1
Read the current end user
User.get() is typed UserDataInstance | null, so narrow it before use. The instance has two parts: _luaProfile, the platform’s read-only identity, and your own fields, which live on the record directly.src/skills/tools/GetProfileTool.ts
_luaProfile can’t be written; assignments to it are ignored.2
Store what you learn
user.patch sets and removes top-level fields in one request; user.update merges fields; direct assignments stay local until user.save(). The record is per agent and shared by the sandbox and production.src/skills/tools/LinkAccountTool.ts
_luaProfile.userId in your own system when you first meet a person; it is the key that finds them again from outside a conversation.3
Reach the end user outside a conversation
A webhook, trigger, or job has no current end user: a bare
User.get() fails there in deployed code and resolves to you under lua test. Pass the id you stored, or look the person up by email or phone; null means nobody matched.src/webhooks/AccountSuspendedWebhook.ts
user.send posts into the person’s conversation on the channel they last used: WhatsApp, Messenger, Instagram, a Teams personal chat, MessageBird, or SMS, and the web widget always receives a live copy; a person whose last channel was Slack, Front, iMessage, or RCS can’t be reached this way. In a deployed agent the call resolves true whether or not the message was delivered, and under lua test it throws on failure, so never branch on its return value; confirm delivery in lua logs. To pick a channel, reach someone with no prior conversation, or get a deliveryId for Channels.getStatus, use Channels.send.4
Keep each account's data separate
Data has no tenant concept, so put the account id on every entry you write and filter by it on every read. Take the id from the end user’s record, never from a tool argument: an argument is whatever the model was told; the record is what your code verified.src/skills/tools/ListAccountTicketsTool.ts
accountId check in a skill condition (Write skill context).5
Register the skill and the webhook
Tools are compiled only from a skill on The agent file below is the quickstart’s; if yours differs, add only the highlighted lines to your own
LuaAgent.skills, and the webhook only from LuaAgent.webhooks.src/skills/accounts.skill.ts
LuaAgent.src/index.ts
6
Verify
lua test runs as you, the signed-in developer, so the write lands on your own record for this agent and get_profile returns your profile.Output
lua test --ci skill --name get_profile --input '{}' next: accountId reads acct_abc123, and emails lists the address you signed in with. Then run the webhook with an address nobody has.Output
userId from get_profile instead, user.send throws Failed to send message unless you have a conversation on a channel, and the run prints { status: 'error', error: 'Failed to send message' }.7
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
Look an end user up by phone
User.get({ phone: '+15551234567' }) accepts the number with or without the leading +. When both email and phone are given, email wins. To read the person’s turns with this agent, call User.getChatHistory() (User reference).
If it isn’t working
User.get() fails in a webhook or job
User.get() fails in a webhook or job
There is no current end user in those contexts. Pass the id you stored,
User.get(userId), or look the person up with { email } or { phone } and handle null.The record is empty on a channel where the user already talked to the agent
The record is empty on a channel where the user already talked to the agent
Neither the phone number nor the email was on the profile yet, so the channel identity resolved to a new one. Ask for a verified contact detail, or store your own key in both places and look up by it.
An unset field is still there
An unset field is still there
update() only merges. Use patch({ unset: ['field'] }) or unset('field'), which remove top-level fields; a field set to null is stored as null.Next steps
User reference
get, lookups, patch, send, Inbox, and every type.
Execution contexts
What the current user resolves to in each place your code runs.
Send proactive messages
Reach a person on a chosen channel, and the WhatsApp 24-hour window.
Store and search data
Records that aren’t about one person.

