Behavior Patterns
Chronicle mines the event history of a store for recurring behavior — combinations of context that keep leading to the same action — and lets you ask what usually happens in a situation. The answer is backed by what actually happened, not by a guess.
The question it answers is: given this user, this day, this time of day, what does this person normally do? An agent, a workflow, or the Workbench can ask it and act on a real answer, or learn that there is no established behavior and say so.
What a pattern is
Section titled “What a pattern is”A behavior pattern is a combination of facets that recurred often enough and reliably enough to survive:
| Part | Meaning |
|---|---|
| Grouping key | The scope the behavior belongs to — normally the user |
| Facets | The contextual dimensions the pattern constrains, and their values |
| Occurrences | How many times it has been observed |
| Confidence | How often it holds when its context is present, 0 to 1 |
| Support | The share of all observed events it was seen in, 0 to 1 |
| Weight | Recency-weighted strength — decays as the behavior goes unseen |
| First seen / last seen | When it was first and last observed |
A pattern such as { Day: Monday, TimeBucket: Morning, CommandType: ApproveExpenseReport } with a confidence of 0.9 reads as: on Monday mornings, this person approves expense reports nine times out of ten.
Facets
Section titled “Facets”Facets are read off an event’s context — never its content — so the same vocabulary applies to every event type in every event store.
| Facet | Where it comes from |
|---|---|
CommandType | The command that produced the event; the event type when nothing above it named itself |
InitiatorType | User, Agent, System or Unknown |
InitiatorId | The identity that caused the event |
OnBehalfOf | The identity it acted for, when it acted for someone else |
CausedByCommand | The command one level up the causation chain |
CorrelationRootId | The correlation the event belongs to |
AggregateType | The event source type the event was appended to |
Year, Month | Taken from the event’s occurred timestamp |
Day | Day of week |
TimeBucket | EarlyMorning, Morning, Midday, Afternoon, Evening or Night |
Every time-derived facet comes from the event’s own occurred timestamp, never from wall-clock time at processing. A backdated append and a replay both land in the bucket the event actually belongs to.
Who the behavior belongs to
Section titled “Who the behavior belongs to”An agent acting on behalf of a person contributes to that person’s behavior, not its own — otherwise one habit would be split across every agent that happened to carry it out. The agent is still recorded as the initiator, so agent-driven and user-driven behavior stay distinguishable.
An event nobody can be named for is not mined at all. Its behavior belongs to no scope, and counting it into a catch-all that every unattributed append pours into produces noise rather than a pattern.
How mining works
Section titled “How mining works”Chronicle does not store anything per event. It keeps a bounded Lossy Counting sketch per scope:
- Each event’s facets are expanded into every combination up to
MaximumCombinationSize(3 by default). The cap is what keeps the candidate space polynomial rather than exponential. - Each combination is counted in the sketch. Memory is bounded by the
Errorparameter regardless of how long the stream runs — nothing with a true frequency above the support threshold is ever missed. - A combination that goes unseen decays: its weight is multiplied by
DecayFactorfor every day since it was last seen, so behavior that stopped happening sinks below the threshold and is pruned instead of competing forever with what a person does now. - Only combinations clearing both
MinimumSupportandMinimumConfidenceare persisted.
Storage therefore scales with distinct recurring behavior, not with event volume. A store that appends millions of events but sees a few hundred recurring behaviors holds a few hundred rows.
Confidence
Section titled “Confidence”Confidence reads as the rule “in this context, this action follows”: the frequency of the whole combination over the frequency of the same combination without its CommandType facet. A combination that names no action is pure context, and its confidence is its support.
Configuration
Section titled “Configuration”Under Cratis:Chronicle:PatternDetection:
| Setting | Default | What it does |
|---|---|---|
Facets | CommandType, InitiatorType, CausedByCommand, AggregateType, Day, TimeBucket | Which facets take part in the mined combination |
MaximumCombinationSize | 3 | The largest number of facets a combination may hold |
Error | 0.001 | The Lossy Counting error parameter — smaller buys accuracy with memory |
MinimumSupport | 0.01 | The smallest share of events a combination must hold to survive |
MinimumConfidence | 0.5 | The smallest confidence a combination must hold to survive |
DecayFactor | 0.99 | Daily decay applied to a combination that has gone unseen |
Year and Month are deliberately absent from Facets: they are kept on a surviving pattern for recency, but combining them multiplies the candidate space by every month the store has been running while splitting one behavior across all of them. Add them when a deployment wants to mine seasonality and can afford the cardinality.
Naming the command
Section titled “Naming the command”For CommandType and CausedByCommand to be meaningful, something above the event has to name the command. Cratis Arc records a Command causation naming the executing command for the duration of that command, so nested commands produce a real “caused by” chain and sibling commands do not. Anything else appending to Chronicle can do the same by putting a commandType property on its causation.
Without such a link the mined CommandType falls back to the event type, which is still a meaningful action in an event-sourced store — the fact that was recorded is what happened.
Querying patterns
Section titled “Querying patterns”Ask what usually happens with the .NET client’s pattern API, or over gRPC through the Patterns service:
GetPatterns— given a partial context, the patterns that apply, ranked by specificity and then confidence.GetPatternsForScope— everything a scope has established, unfiltered, for browsing.
Specificity outranks confidence in the ranking. A pattern constraining everything you asked about answers your question; a broader, more confident one answers a question you did not ask.
Nothing clearing the confidence bar returns nothing. An empty answer is a true statement — this scope has no established behavior for this context — and is not padded with the best of a bad set.