Skip to main content
Workflows often need reference data: a lookup table, a prompt library, a corpus of documents to search. Maitai offers two mechanisms, and the right one depends on size and access pattern. A rule of thumb: if you’ll hold the entire file in memory and index into it yourself, use an accessory. If you need to search or selectively fetch from a large collection, use a datastore.

Accessory files

An accessory is a single JSON file you upload alongside your workflow under a short key. At runtime, ctx.load_accessory(key) returns its parsed contents.
How the key maps to storage. When you upload an accessory under key hts_codes, Maitai stores the file in S3 and sets an env var HTS_CODES_S3_KEY in the workflow’s config. load_accessory("hts_codes") reads that env var to locate the object. The key is uppercased and hyphens become underscores (hts-codes becomes HTS_CODES_S3_KEY), so keep your load_accessory key identical to the upload key. Resolution order. load_accessory checks, in order: in-process cache, then Redis (24h TTL), then S3 (then writes back to Redis). If no S3 key is configured and you passed fallback_path, it loads that local file, convenient for local development before the accessory is uploaded. Limits. Up to ~50 MB per accessory and 100 accessories per workflow. Keys are lowercase alphanumeric plus _/-. Files must be valid UTF-8 JSON. See Uploading a workflow for the --accessory flag.

Datastores

A datastore is a queryable collection of JSON records. You define which fields to index and how; Maitai stores the raw records in S3 and builds a search index over the indexed fields. At runtime your workflow queries it through ctx.datastore (by record id, by keyword/tag filter, or by vector similarity) without managing any of the underlying infrastructure. Each workflow has at most one datastore. (Need several datasets? Split them across workflows and compose with nested calls.)

Schema

A datastore is defined by a small schema: which field is the record key, where the raw records live, and how each indexed field is treated.
Field types: Only the fields you list are indexed; ctx.datastore.get always returns the complete record from S3.

Querying

Import the filter helpers from maitai_workflow and pass a filter as where. Helpers compose with all_/any_/not_ (or the & | ~ operators).
  • Tag(field, values, prefix=False), match a tag field against one or more values (OR). prefix=True matches by prefix ("6202" matches 6202.10.4080, …).
  • Text(terms, field=None, op="any"), full-text search. op="any" (OR, recall), op="all" (AND, precision), or op="raw" for verbatim RediSearch (phrases, leather*, etc.).
  • Raw(expr), drop in a literal RediSearch fragment for anything the DSL doesn’t model (numeric/geo ranges).
search returns only the indexed fields by default; pass full=True to hydrate the complete records from S3 (slower). With a vector, results are sorted by similarity; without one, by text relevance.

Creating a datastore

  1. Define the schema for the workflow (a YAML/JSON document like above):
  2. Upload records: a JSON array of records, or an S3 URI to ingest:
    Maitai stores the records, computes/loads the indexed fields, and builds the search index in the background. Once ready, workers serve ctx.datastore queries against it.
Manage datastores with GET /workflows/{workflow_id}/datastores and DELETE /workflows/{workflow_id}/datastores/{name}.

Uploading a workflow

Register a workflow (and bundle its accessories) with the upload script, run from the repository root:
This uploads the script to S3, upserts the workflow row (timeout, execution mode, application binding), and uploads any accessory files next to the script. Callers then invoke it as model="workflow:<workflow_ref_name>".
Use company_id = -1 for a global workflow. Run from maitai-backend/ so relative paths like ./workflows/... resolve, with AWS + database credentials configured for your target environment.

Execution modes

Next