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.

Action

What happened?

action

Scope

Whose behavior is this?

scope.*

Item

What was it done to?

items[].*

Context

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

action

What happened?

The verb, normalized.

completedviewedstartedcancelled

Scope

scope.*

Whose behavior is this?

The identity grain, named explicitly.

useraccountdevicehousehold

Item

items[].*

What was it done to?

The objects of the action.

productarticlebookingvideoplan

Context

context.*

Under which conditions?

The circumstances of the action.

devicechannellocalereferrertime of day

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.

Raw payload
{
"event": "order.completed",
"created_at": "2026-08-03T14:22:09Z",
"customer": {
"id": "cus_8fJ2K",
"email": "r.okafor@example.com"dropped
},
"line_items": [{
"sku": "SKU-4417",
"category": "dry-food",
"price": 42.50
}, {
"sku": "SKU-2201",
"category": "treats",
"price": 8.00
}],
"currency": "EUR",
"user_agent": "Mozilla/5.0 (iPhone; ...)",
"locale": "en-GB",
"internal_flags": ["retry_1"]dropped
}
ASIC event
{
"timestamp": "2026-08-03T14:22:09Z",
"action": "completed",
"scope": {
"grain": "user",static
"id": "cus_8fJ2K"
},
"items": [
{
"type": "product",static
"id": "SKU-4417",
"category": "dry-food",
"value": 42.50,
"currency": "EUR"
},
{
"type": "product",static
"id": "SKU-2201",
"category": "treats",
"value": 8.00,
"currency": "EUR"
}
],
"context": {
"device": "mobile",
"locale": "en-GB",
"channel": "web"static
}
}

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.

high affinity: dry foodmixed category buyersingle category only

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.

mobile onlycross deviceweekend activeevening active

Enrichment example: RFM

value scoring

RFM 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 inputRead fromAggregation
Recencyaction + timestamptime since last
Frequencyaction, over a windowcount
Monetaryitems[].valuesum

Conventional RFM labels

recently active, high frequency, high valuerecently active, high frequency, low valuerecently active, low frequency, high valuerecently active, low frequency, low valueslowing, high frequency, high valueslowing, high frequency, low valueslowing, low frequency, high valuelapsed, high frequency, high valuelapsed, high frequency, low valuelapsed, low frequency, high valuelapsed, low frequency, low value

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.

  1. 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.

  2. 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.

  3. 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.

PathTypeRequirementDescription
asic_versionstringoptionalFormat version. "1.0".
timestampstring (RFC 3339)requiredWhen the action occurred, in UTC.
actionstringrequiredNormalized past-tense verb. Lowercase, snake case.
scope.grainstringrequiredThe identity level this behavior belongs to.
scope.idstringrequiredIdentifier at that grain.
scope.parent_grainstringoptionalHigher grain the scope rolls up into.
scope.parent_idstringoptionalIdentifier at the parent grain.
items[].typestringrequiredClass of one object acted upon.
items[].idstringrequiredIdentifier of the object.
items[].categorystringoptionalGrouping used for affinity segments.
items[].valuenumberoptionalValue carried by one item. Supports value aggregation.
items[].currencystring (ISO 4217)optionalCurrency of items[].value.
items[].quantitynumberoptionalHow many of the item the action covered.
context.*flat objectoptionalCircumstances only. Scalar values, one level deep.
sourcestringoptionalSystem the raw payload came from.
raw_refstringoptionalPointer 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.

TypeScript
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",
    },
  };
}
Python
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",
        },
    }
SQL
-- 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 item and context, and the requirement that scope names 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 in scope with an explicit grain. The products land in items. The device, locale, and channel land in context.

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.