Version 1.0 · Free to implement
ASIC
action · scope · item · context
A behavioral event format with four dimensions. Every event names what happened, whose behavior it is, what it was done to, and under which conditions.
What happened?
action
Whose behavior is this?
scope.*
What was it done to?
items[].*
Under which conditions?
context.*
Every product emits events, and every product invents its own shape for them. One team sends order_completed, another sends purchase, a third sends checkout_success with the interesting parts buried in a free-form properties object. Analytics tools accept all of it, because accepting anything is how they win adoption.
The bill arrives later. Once events have no common shape, nothing downstream can be general. Every metric is bespoke. Every segment is a hand-written query. Every migration to a new tool is a remapping project. Teams end up with warehouses full of behavioral data and no behavioral logic, because the logic has to be rebuilt by hand for every question anyone asks.
The usual response is a schema registry: enforce the shapes you already have. That makes events consistent without making them comparable, because two well-documented events with different structures are still two different structures.
Action
actionWhat happened?
The verb, normalized.
Scope
scope.*Whose behavior is this?
The identity grain, named explicitly.
Item
items[].*What was it done to?
The objects of the action.
Context
context.*Under which conditions?
The circumstances of the action.
The one structural rule
item and context must not be merged. Most schemas collapse both into one properties bag, and that single decision is what makes the bag useless. What was interacted with and under which circumstances are different kinds of fact, and they answer different questions. Keeping them apart is what makes segmentation derivable.
Plus a timestamp, which every event has and which does more work than people expect.
Metrics become a closed vocabulary. Count the actions, sum an item value, count distinct items, measure time since the last action.
Comparison works across sources. A purchase from a web store and one from a mobile app land in the same shape, so one metric definition covers both.
Portability becomes real. An ASIC event is plain JSON with four named blocks. No proprietary types, no vendor SDK required.
An order webhook. Two line items become an items array; one static value added (the channel); two fields deliberately dropped.
Hover a field to see where it lands
Because the dimensions did the structuring in advance, four aggregations cover most of what teams compute. Point at an event type, get the number.
count
action
How often it happened.
sum
items[].value
Value accumulated.
count distinct
items[].id / items[].category
Breadth of interaction.
time since last
action + timestamp
Recency of behavior.
Because ASIC separates the object from the conditions, segmentation is derivable from the format rather than authored by hand. The two segment families come straight from the item and context dimensions.
Item segments: affinity
What a profile repeatedly interacts with. Enumerate the values that appear in items[].* for one profile, compute the share, and a profile whose interactions concentrate in one category is a category-affinity segment. No query written, no segment designed.
Context segments: situation
The circumstances under which a profile acts. The same computation on context.* plus the timestamp. This is the cheapest family and the most consistently ignored one, because in most schemas the context is scattered and unusable.
Enrichment example: RFM
value scoringRFM is not a segment family in ASIC. It is a standard value scoring model you can layer on top of item and context segments. It reads from the same dimensions: action plus timestamp gives recency and frequency; items[].value gives monetary. In e-commerce it is a common way to extend an affinity or situation segment with a value score.
| RFM input | Read from | Aggregation |
|---|---|---|
| Recency | action + timestamp | time since last |
| Frequency | action, over a window | count |
| Monetary | items[].value | sum |
Conventional RFM labels
These labels illustrate how a value model can accompany dimension-derived segments. They are not ASIC segment names.
The naming rule: a segment value describes behavior, never identity
Store high share of interactions with licensed content, not superfan. Store mobile only, evening active, not night-owl subscriber.
More accurate
Three purchases of licensed content makes someone a repeat buyer of licensed content. It does not make them a fan. The descriptive string claims only what the data supports.
Legally safer
A behavioral label attached to a person is profiling under GDPR and comparable regimes. A description of observed behavior is defensible in a data protection review. An inferred persona, particularly one implying age, gender, or wealth, is not.
Self-documenting
Any operator, in any tool, reading the field for the first time knows what it means. No lookup table, no tribal knowledge.
ASIC is not an AI format. The defensible claim is smaller: structured behavioral data is what makes a large language model useful against behavior, and unstructured events are what make it fail.
Four named positions are a stable contract for tool calls. An agent querying behavior needs to know the shape in advance. Against arbitrary schemas it must discover fields, guess names, and fail silently when wrong. Against ASIC, count actions of type X on items in category Y is expressible without discovery.
The dimensions are a natural prompt structure. action, scope, item and context map to what, who, which thing, and under which conditions, which is the way a behavior question is asked in plain language. Translating intent into a query is mechanical with four known slots and guesswork without them.
Descriptive segment values are already model-readable. high share of interactions with licensed content needs no lookup table. A coded label like seg_04 or a persona like superfan must be explained on every call. The naming rule pays off twice.
ASIC was not designed for language models. It was designed so behavior is readable by machines, and that turns out to be the same requirement.
A segment value stored as a descriptive string can be watched for change. The transition carries more information than either state: previous value, new value, timestamp, direction. A profile moving from high share of interactions with licensed content to low share of interactions with licensed content is the single most important thing that can happen to it, and no threshold on any individual metric sees it happen.
from high share of interactions with licensed content
to low share of interactions with licensed content
at 2026-08-03T00:00:00Z
direction negative
The format makes the state observable, and the state changing is the event that matters.
| Path | Type | Requirement | Description |
|---|---|---|---|
| asic_version | string | optional | Format version. "1.0". |
| timestamp | string (RFC 3339) | required | When the action occurred, in UTC. |
| action | string | required | Normalized past-tense verb. Lowercase, snake case. |
| scope.grain | string | required | The identity level this behavior belongs to. |
| scope.id | string | required | Identifier at that grain. |
| scope.parent_grain | string | optional | Higher grain the scope rolls up into. |
| scope.parent_id | string | optional | Identifier at the parent grain. |
| items[].type | string | required | Class of one object acted upon. |
| items[].id | string | required | Identifier of the object. |
| items[].category | string | optional | Grouping used for affinity segments. |
| items[].value | number | optional | Value carried by one item. Supports value aggregation. |
| items[].currency | string (ISO 4217) | optional | Currency of items[].value. |
| items[].quantity | number | optional | How many of the item the action covered. |
| context.* | flat object | optional | Circumstances only. Scalar values, one level deep. |
| source | string | optional | System the raw payload came from. |
| raw_ref | string | optional | Pointer to the stored raw payload. |
Validate against the JSON Schema: /asic-1.0.schema.json
A mapping is a function. Read the raw payload, decide which fields carry meaning, place them in the four positions. Copy one of these and edit the field names.
type AsicEvent = {
asic_version: "1.0";
timestamp: string;
action: string;
scope: { grain: string; id: string; parent_grain?: string; parent_id?: string };
items: { type: string; id: string; category?: string; value?: number; currency?: string; quantity?: number }[];
context?: Record<string, string | number | boolean>;
};
export function toAsic(payload: OrderCompleted): AsicEvent {
return {
asic_version: "1.0",
timestamp: payload.created_at,
action: "completed",
scope: { grain: "user", id: payload.customer.id },
items: payload.line_items.map((line) => ({
type: "product",
id: line.sku,
category: line.category,
value: line.price,
currency: payload.currency,
})),
context: {
device: payload.user_agent.includes("iPhone") ? "mobile" : "desktop",
locale: payload.locale,
channel: "web",
},
};
}def to_asic(payload: dict) -> dict:
return {
"asic_version": "1.0",
"timestamp": payload["created_at"],
"action": "completed",
"scope": {"grain": "user", "id": payload["customer"]["id"]},
"items": [
{
"type": "product",
"id": line["sku"],
"category": line["category"],
"value": line["price"],
"currency": payload["currency"],
}
for line in payload["line_items"]
],
"context": {
"device": "mobile" if "iPhone" in payload["user_agent"] else "desktop",
"locale": payload["locale"],
"channel": "web",
},
}-- Top item categories by scope over the last 30 days.
select
scope.id as scope_id,
item.category,
count(*) as interactions,
sum(item.value) as total_value
from asic_events
cross join unnest(items) as item
where timestamp >= current_date - interval '30' day
group by scope.id, item.category;- This is just subject, verb, object with extra steps.
- Fair, and the ancestry is real. The addition is the split between
itemandcontext, and the requirement thatscopenames its grain explicitly. Those two constraints are what make the format generative rather than merely tidy. - How is this different from a track call?
A track call from an event emitter is an event name plus a flat properties object. The name is free text, and the same properties bag mixes the item, the actor, the device, and every other circumstance together.
Raw track call { "event": "Order Completed", "properties": { "order_id": "ord_123", "line_items": [ { "sku": "SKU-4417", "category": "dry-food", "price": 42.50 }, { "sku": "SKU-2201", "category": "treats", "price": 8.00 } ], "currency": "EUR", "customer_id": "cus_8fJ2K", "user_agent": "Mozilla/5.0 (iPhone; ...)", "locale": "en-GB" } }ASIC takes the same facts and assigns them to fixed positions. The event name becomes a normalized verb in
action. The actor lands inscopewith an explicit grain. The products land initems. The device, locale, and channel land incontext.Same event in ASIC { "action": "completed", "scope": { "grain": "user", "id": "cus_8fJ2K" }, "items": [ { "type": "product", "id": "SKU-4417", "category": "dry-food", "value": 42.50, "currency": "EUR" }, { "type": "product", "id": "SKU-2201", "category": "treats", "value": 8.00, "currency": "EUR" } ], "context": { "device": "mobile", "locale": "en-GB", "channel": "web" } }The data is identical. The contract is different. A track call says "send any name with any properties." ASIC says "name the action, name the scope grain, and keep the object separate from the circumstances." You can keep emitting track calls and map them into ASIC at the edge; that is a transformation, not a migration.
- What about events that do not fit?
- Some do not. Pure system telemetry with no actor has no meaningful scope. Financial transactions between two parties have two scopes and the format expresses that awkwardly. ASIC is a format for behavioral events, not a universal event format.
- Does something get lost in the mapping?
- Yes, by design. Mapping forces a decision about which fields carry meaning. Keep the raw payload, and treat ASIC as the queryable layer over it rather than a replacement.
- Do I need Chordis to use this?
- No. It is plain JSON, implementable in any stack, in an afternoon.
ASIC is published and maintained by Chordis, and it is free to implement. The format is vendor neutral: nothing here requires a Chordis account, and the schema and examples are usable in any stack.