Thousands of alerts.
The few that matter.
Security teams drown in alerts that are almost all noise. Here a decision model on a Chalk GPU triages a live queue: it reads each alert with its context, decides whether to close, watch, investigate or contain, shows which facts moved the call, and adapts when an analyst pushes back.
Watch the queue triage itself
A new alert arrives every few seconds: impossible travel, privilege grants, bulk downloads, token reuse. Pick one to see the record the model read, its decision, and the facts that moved it. Then push back: allowlist the source or confirm it malicious, and watch it re-triage.
Halcyon Freight, its people, hosts and alerts are fictional; every risky alert has a benign twin that differs only in context. The triage is real: each alert is decided live by laya-typed-decisions, a 421M-parameter ModernBERT-large model fine-tuned on security, support, invoice and agent-trace decisions, on one L4. Contributions are counterfactual: the same alert re-asked with one fact changed. The queue action is a stated policy over the calibrated probability: contain when the model says so, investigate at 50% or more, monitor from 30%, close below. Everyone sees the same queue, and feedback expires after 15 minutes.
How it works
A detection rule knows what happened. Deciding whether it matters takes context: who did it, from where, to what, and whether anyone expected it.
Enrich
Each raw alert is joined with what the detection rule never sees: the identity directory, how critical the asset is, the source allowlist, open change windows, how many other alerts fired on the same user or host in the last hour, and recent analyst verdicts.
Decide and explain
A decision model answers three typed questions in one pass: disposition, P(malicious) and severity. The same batch re-asks with each fact flipped, so every decision comes with the features that moved it, measured rather than guessed.
Learn from analysts
When an analyst allowlists a source or confirms an alert, that becomes a feature on the next similar signal. The queue adapts in seconds, with no retraining, and every tuning expires unless someone renews it.
A record in, a decision out
The model reads an alert as a JSON record and answers questions you write in plain language. Explanations are the same call in a batch, with one fact flipped per variant.
To run your own copy, clone the repo and run the deploy script. Chalk builds the image with the weights baked in, provisions an L4, and prints the URL of your queue.
chalk-ai/siem-triageimport laya
agent = laya.load("michaelfeil/laya-typed-decisions", device="cuda")
# The SIEM alert, enriched by the triage service with directory and stream context.
record = {
"alert": {
"rule": "brute_force",
"description": "many failed authentications followed by a success",
"evidence": "46 failed logins for admin r.batra came from Lagos, NG, followed by "
+ "a successful login after an MFA push was approved. Source 102.89.32.10 is "
+ "not a recognised address. No change window was active.",
},
"context": {
"asset_criticality": "crown_jewel",
"change_window_active": False,
"source_on_allowlist": False,
},
"history": {"prior_alerts_90d": 1, "distinct_countries_30d": 1, "logins_30d": 140},
"principal": {"name": "r.batra", "type": "admin", "mfa_enrolled": True},
}
questions = {
"disposition": {
"type": "choice",
"instructions": "How should this security alert be dispositioned?",
"criteria": {
"close_benign": "Expected, explainable activity; close without analyst time.",
"monitor": "Not clearly malicious, but worth watching for recurrence.",
"investigate": "Warrants an analyst opening an investigation.",
"contain": "Contain the host or account immediately.",
},
},
"true_positive": {
"type": "noul", # a statement; the answer is P(true)
"instructions": "This alert reflects genuinely malicious or unauthorised activity.",
},
}
answers = agent.predict(record, questions)["answers"]
answers["disposition"]["choice"] # "investigate"
answers["true_positive"]["noul"] # raw P(malicious); calibrate before thresholdingTriage you can audit
Every closed alert is a decision someone may have to defend. Each one here comes with a probability, a reason, and a record of what it saw.
Calibrated, not just ranked
Raw probabilities are recalibrated on the model’s own training split, so 80% means roughly four in five. Threshold on it, and send the uncertain middle to a person.
Explanations you can check
Contributions come from counterfactual passes: the same alert with one fact changed. If allowlisting the source drops the score by 40 points, that is what the model did.
Features from the stream
Windowed counts over the alert stream itself, such as alerts on this user in the last hour, are computed as signals arrive and read by the model with everything else.
One batched call per alert
On one L4, an alert’s three questions plus every counterfactual run in a single batched call of about a third of a second. A noisy queue costs the same GPU as a quiet one.
Runs in your cloud
The model, the context and the alerts stay in your Chalk environment. Nothing about your users or hosts is sent to a third-party API.
Open source
The triage service, the signal generator and the deploy script are Apache-2.0 on GitHub. Point it at your own alert stream and directories.
Spend analyst time on the alerts that matter
Run triage, risk and routing models next to your security data, in your own cloud. Talk to an engineer about your queue.