Build
Anatomy of a component
A component is a small folder with two things: an entrypoint (your code) and a description file (meta.yaml). The twist that makes it easy: the code is the source of truth for the contract. You write the code; aos check derives what the component reads, writes, calls, and needs β and writes it into meta.yaml for you.
1 · The entrypoint (your code)
You write ordinary Python against the SDKβs one I/O object, ctx. A driver marks its entrypoint with @driver.main and runs to completion:
from aos import driver, ctx, egress
egress("api.sensors.example.gov", rate_per_min=120, secrets=["SENSOR_API_KEY"])
@driver.main
def run():
aoi = ctx.get(AOI_ID, "space.aoi") # a READ -> space.aoi
resp = ctx.fetch("https://api.sensors.example.gov/v1/readings") # a CALL (broker injects SENSOR_API_KEY)
ctx.write_many(resp.json()["rows"], # a WRITE -> air.sensor_reading (bitemporal)
valid_from_field="observed_at")An engine exposes named actions instead β each function marked with @engine.verb, naming the governed interface it implements:
from aos import engine, ctx
@engine.verb("hr.leave_apply@0.1.0") # this action implements a governed interface
def request(req):
ctx.write(leave_record(req)) # uses the same ctx surface
@engine.verb("hr.leave_decide@0.1.0")
def decide(req):
...ctx is your whole safe toolkit β get/facts to read, write/write_many to write, fetch to call an allowed site (the broker injects declared secrets server-side), file for blobs, ai to ask the model. It can only do what the code declared, and the platform enforces that at run time.
2 · The derived contract (meta.yaml)
You never hand-write what the component touches. aos check reads the code above and derives this β you keep only the identity and non-derivable packaging at the top:
component_id: ds:airawatdeveloper:driver/air-sensors # identity β you set this
kind: driver # driver | engine | app | solution | ...
version: 0.2.0
entrypoint: run.py
# -- everything below is DERIVED from the code by `aos check` --
reads: [space.aoi] # from ctx.get / ctx.facts
writes: [air.sensor_reading] # from ctx.write / ctx.write_many
scopes: # reads/writes are the human summary;
- { verb: write, scope: air.sensor_reading } # scopes are the exact grants the kernel enforces
egress: # from egress(...) + ctx.fetch
- { host: api.sensors.example.gov, rate_per_min: 120 }
secrets: [SENSOR_API_KEY] # from egress(secrets=...); supplied from the vault at run timeBecause those lists are derived, they canβt drift from what the code actually does β they are what the code does. The only things you keep by hand are identity and packaging the code canβt imply: the runtime it needs, its config schema, any bundled assets, and optional subscribes: (events it reacts to β the platform auto-wires each into a subscription at install).
For an engine, aos check also derives a verbs: block from the @engine.verb decorators β a map from each action to the governed interface it implements. Publishing the engine self-registers these into platform.verb_binding, governed data the domain-blind host reads to route each request to your engine. No hardcoded list, no deploy.
Build one hands-on in the Quickstart; the full ctx surface is covered in Working with governed data.