resource object describing the agent the records came from, and an array of records.
The machine-readable definition is published as JSON Schema (draft 2020-12) at:
lua-log-batch.schema.json, which you can download or point a $ref at directly; the extensionless form above is the schema’s own $id.
Validate against that file rather than against this page: it is the same file the platform’s own tests run their fixtures through. This page explains what each field means.
The envelope
A batch carries exactly one
resource. Records from three agents in one flush become three batches, not one batch with a mixed resource. Group your ingest by resource['lua.agent.id'] and you never have to reconcile.
Resource
Resource attributes follow the OpenTelemetry semantic conventions where one exists, and are prefixedlua. where one does not.
Record
attributes is flat by design: every value is a string, a number, or a boolean. Destinations that index attributes (Datadog facets, Loki structured metadata, Elastic fields) can take the whole object without a flattening step.
Severity
The mapping is fixed.severityNumber follows the OpenTelemetry log data model, so a destination that understands OTel severity ranges sorts Lua’s records correctly without configuration.
A drain’s
--min-severity compares severityNumber against debug: 5, info: 9, warn: 13, error: 17 and keeps what is greater than or equal.
eventName
^lua\.[a-z_-]+\.[a-z]+$. The name never contains an id: a failing Shopify MCP tool is lua.mcp.error, not lua.mcp_shopify.error. Filter on the name and group by an attribute.
For the eighteen agent sources the suffix is the line’s level:
lua.device.* is the one name in that list nothing emits yet: device work is logged under device-trigger, so a drain that selects device receives no lua.device.* records. The name is reserved and will start appearing within this major version, which is why a consumer switches on eventName with a default branch.
For execution records the suffix is the phase:
lua.log.type, lua.source, and lua.drain.id, and then what the table names:
lua.source follows the name: the source segment of lua.drain.test is drain, and of lua.truncated.warn it is truncated. So a query for everything the drain said about itself is lua.source = "drain", and it never catches a record one of your agents wrote.
Each synthetic record also carries an attribute no agent record has — lua.drain.id on the three lua.drain.* records, lua.truncated.lines on the truncation one — which makes them cheap to select without a wildcard match on the name. The monitor packs rely on that.
Synthetic records do not count against the drain’s daily quota, and they do count in health.deliveredCount24h.
Attributes
Attributes are present when the platform has the value and absent otherwise. A missing attribute is never an error and never an empty string.traceId and spanId come from W3C trace context when the turn carried it. A malformed value yields neither field; it is not an error and the record is still delivered.
Content records
user_message and agent_response carry end-user content. On a drain without --include-content, those records are still delivered — so you can see that a turn happened, on what channel, for which user — but body is absent and lua.content.withheld is true. Turning content on changes only that: the body appears, still scrubbed.
OTLP mapping
An OTLP drain sends the same information as an OTLP/HTTPExportLogsServiceRequest in JSON encoding. The mapping is one-to-one.
Versioning
The schema URL carries the major version, and theschemaUrl field in every batch tells you which one you received.
Within 1.0, changes are additive only. That means:
- New optional
attributeskeys may appear at any time, on any record. - New
eventNamevalues may appear — new sources, new synthetic records. A consumer that switches oneventNamemust have a default branch. - New optional fields may be added to
resourceand torecords[]. - New
severityNumbervalues within the OpenTelemetry ranges may appear; compare with>=against the boundaries above rather than with==against the four current values.
eventName.
A new major version is published at a new URL — …/schemas/logs/2.0 — and existing drains keep receiving 1.0 until they are moved. Build your ingest so an unknown attributes key is carried through rather than rejected, and a schema addition never pages you.
Changelog
Next steps
Delivery guarantees
At-least-once, batching, retry, and drop semantics.
Verify signatures
The
X-Lua-Signature format and working verifiers.Generic HTTPS
A receiver that validates this schema end to end.
Grafana dashboard
Panels built on these field names.

