Output & Serving
output.yaml — Schema Contract
Section titled “output.yaml — Schema Contract”Declares the shape of data this product produces. This is the contract with downstream consumers. Breaking changes require a major version bump.
apiVersion: akili/v1kind: Output
schema: - name: event_id type: uuid primary_key: true role: identity description: Unique event identifier
- name: user_id type: string nullable: false role: event_key description: User who triggered the event
- name: event_type type: string nullable: false description: Type of user interaction
- name: event_timestamp type: timestamp nullable: false description: When the event occurred
- name: page_url type: string nullable: true description: URL of the page where the event happened
- name: session_duration_ms type: integer nullable: true role: measure description: Duration of the session in milliseconds
format: parquetpartitioning: - field: event_timestamp granularity: daySupported types:
| Type | Notes |
|---|---|
string | Variable length |
integer | 64-bit signed |
float | Double precision |
decimal(p,s) | Precision and scale required |
boolean | |
date | Calendar date |
timestamp | Always stored with timezone (UTC) |
uuid | |
json | Stored as string in Iceberg |
array(T) | Nested type, e.g. array(string) |
map(K,V) | Key-value, e.g. map(string,integer) |
Column roles:
| Role | Meaning | Constraints |
|---|---|---|
identity | Natural/business key | At least one per schema. Immutable across versions. |
attribute | Descriptive property | Default if role omitted |
measure | Numeric fact for aggregation | Must be numeric type |
event_key | Foreign key to another product’s identity | Must reference valid upstream identity |
Caution: Every output schema must have at least one column with
role: identity. Identity columns cannot be removed, renamed, or have their type changed in any version — they are immutable. To change identity structure, create a new product.
Schema evolution rules:
| Change | Version Impact |
|---|---|
| Add nullable column | Minor bump |
| Remove column | Major bump |
| Rename column | Major bump (treated as remove + add) |
| Widen type (int to bigint) | Minor bump |
| Narrow/incompatible type change | Major bump |
serving.yaml — Store Routing
Section titled “serving.yaml — Store Routing”Declares where and how consumers access the output. You state intent; the platform routes to the correct store.
apiVersion: akili/v1kind: Serving
endpoints: - type: lookup description: Point lookups by event_id for the Portal config: index_columns: - event_id - user_id
- type: analytics description: Event analytics for dashboards
- type: realtime description: Latest events per user, refreshed continuously config: key_template: "user:{user_id}:latest" ttl: 24h include_columns: - user_id - event_type - event_timestampStore routing:
| You Declare | Platform Routes To | When To Use |
|---|---|---|
type: lookup | PostgreSQL (upsert, RLS) | Point lookups, Portal API, key-value access |
type: timeseries | TimescaleDB | Time-range queries, trend analysis |
type: analytics | StarRocks (federates to Iceberg) | OLAP, GROUP BY, dashboards |
type: realtime | Redis (SET with TTL) | Sub-10ms reads, streaming hot path |
| (no endpoints) | S3/Iceberg only | Cold storage, time travel, batch |
Note: S3 (Ceph RGW) via Iceberg is always written to, regardless of endpoint configuration. It is the source of truth with full time-travel capability. All other stores are optimized projections.
Type-specific config:
| Type | Key Config | Notes |
|---|---|---|
lookup | index_columns | Columns to index in PostgreSQL. Defaults to primary key. |
timeseries | time_column, chunk_interval | Column for hypertable partitioning. |
analytics | None | Platform registers the Iceberg table in StarRocks. No data movement. |
realtime | key_template (required), ttl, include_columns | Use column_name placeholders in key template (e.g. user:$col:latest). |
A product can declare multiple serving types to serve the same data through different access patterns simultaneously.