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.
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 throughctx.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.
Only the fields you list are indexed;
ctx.datastore.get always returns the complete record from S3.
Querying
Import the filter helpers frommaitai_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=Truematches by prefix ("6202"matches6202.10.4080, …).Text(terms, field=None, op="any"), full-text search.op="any"(OR, recall),op="all"(AND, precision), orop="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
-
Define the schema for the workflow (a YAML/JSON document like above):
-
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.datastorequeries against it.
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: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
- Use this data in your script: Workflow Context (
ctx) - How a workflow file is laid out: Workflow Structure
- Invoke your workflow: Invoking workflows