Skip to main content
After this guide, your tools store records in an agent-scoped collection, read them back by exact filter, and find them by meaning. Data holds many records per agent; for one record per end user, write to the end user’s record instead (Identify users). 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).
  • A test collection name. Collections are shared by the sandbox and production (About environments), so a local run writes the same data the live agent reads.
1

Store an entry with search text

Data.create takes the collection, the object to store, and an optional string that is embedded for semantic search. Put every term a question might use into that string: it is stored beside data as the entry’s searchText and is the only text search() matches against.
src/skills/tools/AddKnownIssueTool.ts
The returned entry exposes the stored fields directly (entry.product) and through entry.data. Store searchText as a plain string in deployed code; the options object is covered under Options you may need.
2

Find entries by meaning

Data.search embeds the query and returns the closest entries as a flat array, best match first, each with a score from 0 to 1. limit is at most 20 and scoreThreshold defaults to 0.6.
src/skills/tools/FindKnownIssueTool.ts
Raise the threshold when unrelated entries come back, lower it when good ones are missing; 0.7 is a sensible start for support text.
3

Filter entries exactly

Data.get takes a Lua Query filter over the stored fields and returns one page as { data, pagination }. Entries here are plain objects, so read fields through entry.data. Pages hold at most 100 entries.
src/skills/tools/ListOpenIssuesTool.ts
A bare value means equality; $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, and $exists are the field operators, and $and and $or combine clauses at the root.
4

Update and delete

Data.update merges the fields you pass into the entry and keeps the rest; pass a replacement string as the fourth argument to re-index it. Data.delete removes one entry by id.
src/skills/tools/ResolveKnownIssueTool.ts
A delete is permanent; there is no recycle bin.
5

Register the tools in a skill

The skill’s context says who may add and resolve issues; only tools in a skill on LuaAgent.skills are compiled.
src/skills/known-issues.skill.ts
The agent file below is the quickstart’s; if yours differs, add only the highlighted lines to your own LuaAgent.
src/index.ts
6

Verify

Store an entry, then search for it with different words. Both runs hit the real collection.
Output
The query shares no word with the stored title, and the match still scores above 0.7. Clean up with resolve_known_issue and "remove": true.
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

Declare an index for filtered fields

When a collection grows large, a filter on a field without an index slows down and eventually fails with an error naming the field. The third argument of create() and fourth of update() also accept { searchText, index: ['product'] }; a nested pair such as [['product', 'status']] declares a compound index. At most 2 fields per index, 3 declarations per call, and 5 indexes per agent; an index is dropped 14 days after its last declaration or filtered read.
Local runs only. The deployed runtime doesn’t accept the options object yet, fails with searchText must be a string, and never forwards index. In deployed code, pass searchText as a plain string and declare indexes from lua test.

Read one entry by id

Data.getEntry('known-issues', id) returns the entry with direct field access and throws when no entry has that id; entry.save() writes back fields you assigned on it.

Share a collection between agents

Data belongs to one agent: no other agent can read it. When several agents of one organization need the same reference data, such as a product catalog one agent keeps up to date and every support agent reads, an org admin creates a shared collection and names which agents may read it and which may write it. Agents then use it with Data.shared(name). Available in lua-cli 3.50.0 and later. A shared collection belongs to the organization, not to any agent. Agents never create one or change who may use it: only an org admin can, through the admin API. Do not store end customers’ personal data in a shared collection; it is organization reference data, and erasing an end user’s data from one agent does not reach it.
1

Create the shared collection (org admin)

An org admin or owner posts the collection’s name and its two lists of agent ids. Find the organization id as orgId, and the agent ids, in lua agents --json --ci.
Every id must be a live agent of the same organization; an empty list is allowed. It answers 201:
Output
2

Change, list or delete it (org admin)

PUT replaces both lists, so send the full lists every time. An omitted installableBy keeps the current one, and [] clears it. GET lists the organization’s shared collections with their lists, sorted by name. DELETE ends access at once, then removes every entry of the collection; it cannot be undone, and it answers { "deleted": true, "purgedEntries": <n> }. Creating the same name again starts an empty collection.
Listing needs the members:manage permission on the organization; creating, changing and deleting need members:assign-role, which admin and owner hold. A change applies to the next Data.shared call. PUT and DELETE on a name the organization does not have answer 404. Every create, change and delete is recorded in the organization’s audit log.
3

Write from the writer agent

Data.shared(name) returns the same methods as Data (collections, create, get, getEntry, update, patch, increment, search and delete), with the same arguments, filters, indexes and semantic search. A shared collection is a namespace: it can hold several collections, here 'products'.
src/jobs/SyncCatalogJob.ts
An entry that Data.shared(...) returns writes back to the shared collection: entry.update(), entry.save() and entry.delete() never touch the agent’s own Data.
4

Read from the reader agents

A reader may call collections, get, getEntry and search; a writer may call those too.
src/skills/tools/FindProductTool.ts

Let a template’s installs join

A template whose agents need a shared collection declares it in its sharedData section (Lifecycle hooks). The template never creates the collection and never adds itself: an org admin lists it once in the collection’s installableBy, and from then on every install of that template in the organization adds its agent to the collection, and every uninstall takes it off again.
  • A template not on the list never joins, whatever it declares.
  • maxAccess: 'read' lets installs join readers only. With write, an install that asks for write joins writers, but only when the person installing holds members:assign-role; anyone else’s install waits for an admin.
  • Every join and leave is recorded in the organization’s audit log, with the template and the person who installed it.
Access and isolation. Every call is checked on the platform against the collection’s lists, using the agent the code runs as. A write by a reader, any call by an agent on neither list, a name that does not exist, and a name that belongs to another organization all reject with the same error, code === 'shared_data_forbidden' (status 403), so an agent can never learn about, or reach, another organization’s shared collections. Audit. Every write records the agent, the collection and the entry id in the organization’s audit log, never the stored value. Billing. Shared collections follow the usual Data rules, and the embedding work for searchText and search() is billed to the agent that made the call, not to the organization’s other agents. Limits. The same limits as Data apply per call (Data limits); the index limits count per shared collection. A shared collection name is 1 to 128 of A-Z a-z 0-9 _ . -, and each list holds up to 200 agent ids. Test sessions and lua test. A test session of a deployed agent may read a shared collection but not write to it: a write rejects with shared_data_test_session_write_refused (409), so a test never changes the data every other agent reads. Under lua test and other local runs there is no organization list to check, so every call rejects with shared_data_unavailable_offline. Test the reading and writing code against a deployed agent, and keep the logic that shapes the data in plain functions you can test locally.

If it isn’t working

Only searchText is embedded, not data. Include the fields you expect people to search by, and re-index existing entries with Data.update(collection, id, {}, newSearchText).
get() returns plain entries: read entry.data.title. search(), create(), and getEntry() return instances with direct access.
The filter used an operator or a depth outside the grammar. The codes and limits are on the Lua Query page.
Cause The agent is not on the collection’s readers or writers, a reader tried to write, or the name is misspelt or belongs to another organization; the platform reports all of these the same way. Fix Have an org admin list the organization’s shared collections and check the name and that the agent’s id is on the right list.
Cause The call ran under lua test or another local run, which cannot check the organization’s lists. Fix Run it on a deployed agent.
Cause The turn is a test session, which may read a shared collection but never write it. Fix Write from a regular turn, a job or a workflow of the deployed agent.

Next steps

Data reference

Every method, return shape, limit, and error.

Lua Query

The full filter grammar and its limits.

Identify users

One record per end user, and keeping accounts apart.

Add knowledge

For documents the agent should answer from, use knowledge instead.