Elements
Everything you build in Triform is an element. An element has a type
(python, sql, view, circle, …), belongs to a category (actions, data,
frontend, …), and exposes operations you invoke over a flat URL:
/api/{circle}/{slug} # the element
/api/{circle}/{slug}/ops/{op} # one of its operations
An element is the declarative unit of the platform. Its type, its settable properties, the operations it exposes, the resources it provisions, and the errors it can return are all declared in chemistry YAML — not coded by hand. The same shape describes a Python function, a Postgres-backed table, a LinkedIn connector, and a circle itself: one type column, one spec, one set of ops.

The element-agnostic contract
Element behavior is defined entirely in chemistry YAML and flows through generated code — the runtime never branches on element type.
Each element type is authored as a set of .triform/*.yaml files
(definition.yaml names the type and its category, properties.yaml the spec
fields a user or agent edits, ops.yaml the operations, contract.yaml what it
may contain, plus runtime/validation/pricing/observability). Running
chemistry/generators/build-all.sh turns that YAML into generated registries,
routes, and dispatch tables that physics and portal consume. The contract is a
lockstep: change the YAML, regenerate, and the new behavior appears everywhere at
once — there is no second place to edit.
This is why physics and portal contain no element-specific logic. The backend
that executes an operation and the UI that renders an element do not know what a
sales-board is; they look the type up in the generated dispatch map and act
generically. New element types are added by writing YAML and regenerating, never
by adding a match arm in Rust. Generated code under chemistry/generated/ is
overwritten on every build and must never be hand-edited.
The category is metadata, not a URL segment. Categories such as actions,
data, and io exist for dispatch, search, UI grouping, and the modifier
security gate — they are columns the platform reads, never path components. Every
element addresses the same way: /api/{circle}/{slug}, with its operations under
/ops/{op}. There is no library/{category}/{type} prefix and no alias router;
one URL form serves every element type. Because operation lists and input schemas
are generated from the same YAML, the runtime, the docs, and agent tools stay
aligned by construction.

Most elements are atoms — a single flat row in their circle. A few are
compound: an automation parents its condition/loop/wait steps and a
lab parents its brain/ears/mouth. These are the legitimate exceptions
where one element nests under another; everything else sits flat. How elements
relate — containment versus bonds versus dispatch-by-lookup — is the subject of
composition.
Categories
Every element type lives in exactly one category. The categories are a map of what you can build:
| Category | What it is for | Example types |
|---|---|---|
| foundation | Identity and tenancy primitives | circle, circle-ref |
| agents | AI agent runtimes | claude-code, codex, open-code |
| actions | Code you run | python, javascript, rust-fn, hitl |
| apps | Deployable apps and orchestrations | app, automation |
| intelligence | Composed reasoning | lab (brain, ears, mouth) |
| tools | Browsers and platform tooling | chromeless, platform, recorder |
| data | Storage and retrieval | sql, vector, document, files, graph |
| frontend | User-facing surfaces | spa, ssr, view, three-d |
| io | External connectors | http, slack, email, linkedin, queue |
| modifiers | Behaviour applied to other elements | api-token, auth-policy, prompt, rate-limit |
| boards | Structured workspaces | board, sales-board, planning-board |
| connectivity | Element-to-element links | wire |
| diagrams | Visual models | diagram |
Browse the full set, with every type and its operations, in the element catalog.

Related
- Concept: composition — how elements relate
- Concept: runs — how an operation executes
- Concept: tenancy-airtight — where elements live