Skip to main content
Storage is where a server keeps content, and the ResourceManager is how tools and templates read it. The manager loads content by storage id, url, or a raw file — as text or as media — and renders Jinja templates fetched through the active storage provider.

The storage provider

Storage is a provider behind one ABC. The contract owns the Storage ABC and its root-delete guard; the skeleton adds a registry that holds the one active provider. A concrete provider — a local filesystem or object store — ships as a separate plugin and registers through tai42_app when the manifest names its storage_module.

The ResourceManager

The ResourceManager layers on the active provider and resolves a resource from one of three sources:
  • a storage id — content stored under the provider,
  • a url — content fetched from an address, or
  • a raw file — content read directly.
It returns each resource as text or media. Media content is produced as typed content parts, so a resource that is an image or other binary flows through the same interface as text. Accessing the manager before a storage provider is registered raises the moment a stored resource is first touched.

Templates are stored resources

A template is one kind of stored resource. The ResourceManager renders Jinja templates fetched through the provider, with a compiled-template cache in front of the render step. Render mixins add rendered_* methods to the contract’s field models, so a stored template resolves in place wherever a field expects rendered content. A template write, a template delete, a directory delete, or a manual tai templates clear-cache evicts that compiled-template cache on every worker over the worker bus, not only on the process that made the change — so no worker keeps serving a template compiled from stale content.

Locale variants

A template renders against the subject’s locale when the render carries one — the template author never selects a language. A locale-scoped variant lives at "{id}@{subtags}" (for example greeting@he-IL, greeting@he), and a render for a locale walks a fallback chain from the most specific subtag form to the least, then to the bare default id: he-IL resolves greeting@he-ILgreeting@hegreeting, returning the first stored variant. When none of the chain is stored — no variant and no bare default — the render refuses loudly, naming the template and locale, rather than silently rendering the wrong language. A None locale (the locale-agnostic callers — hooks, authz, access control) renders the bare id unchanged. The locale also drives the list_format filter: a template writes {{ names | list_format }} (with an optional CLDR list style, standard by default) and the separator and final connective come from the resolved locale’s CLDR list patterns. It reads the locale the render resolved, never a template argument; a render that resolved no locale, or a locale with no CLDR data, raises loudly.

Managing stored content

Two CLI groups reach stored content. tai templates is the higher-level, template-aware surface (render, cache), while tai storage is the raw provider-resource surface — thin wrappers over the authed /api/storage* routes:
Storage is dead by default — the skeleton ships no provider. With none registered, tai storage info reports the empty state (present: false, a 200 so a UI renders it cleanly) while every CRUD door answers an honest 501 rather than a fabricated default. A resource id must be a relative path with no .. segment; an absolute path or a .. traversal is rejected with a 400 before it reaches the filesystem-backed provider. Upload takes exactly one of --text (verbatim) or --base64 (decoded bytes), and an existing id is overwritten. The presence answer (GET /api/storage / tai storage info) is a core, always-mounted read: it is answerable in every deployment, including one that mounts no storage management surface, so a consumer can always tell “no provider” apart from “cannot ask”. The template-aware surface is the same content viewed as renderable resources:
Stored content is one of the sections the backup surface can export and restore.
A specific storage provider’s configuration — its bucket, credentials, or paths — lives in that provider package’s own repository, reachable from the Plugins section. The platform documents the storage contract, not any one provider.
See the manage-stored-content guide for the media in-and-out workflow and the Python SDK reference for the Storage ABC and the ResourceManager.