BEDIM SECURITY

🎯 How to Use Laya β€” the practical guide

2026-09-27 Β· for the AI SOC deployment (laya 0.3.20, control host + edge host) Β· companion to LAYA-SETUP.md (install) and the 300-report

1 Β· The 60-second mental model

Laya is not a chatbot. It is a decision function: text in β†’ typed answers out, one forward pass, no text generation. You hand it a compact state and a set of typed questions; it returns an answer + calibrated confidence per question in ~0.9 s on CPU:

Lifecycleyour datawazuh alert Β· email Β· ticketstate buildercompact text ≀1024 tokPOST /v1/systemone{state, questions}Laya (1 pass)~0.9 s CPUtyped answerschoice Β· score Β· noulconfidence gateconf β‰₯ Ο„ β†’ act Β· else β†’ gemma LLMlow conf β†’ LLM fallback (gemma :21402)Request lifecycle β€” caller builds a small state, Laya answers typed questions in one pass, the gate decides
TypeAnswerUse it for
choiceone option from your criteria + full probability distributionseverity class, response action, channel routing
scoreposition on a scale you defineurgency, priority, risk level
noulP(answer is yes) β€” a probability, no option pickingis it noise? escalate? reply needed? known pattern?

Never ask Laya to write text, summarize, or reason in steps β€” that's the narrator/gemma path. Laya answers questions you define; the quality of those questions is the quality of the system.

2 Β· Where it lives

EndpointHostNotes
http://127.0.0.1:8099control host8 threads, source repo + evals live here
http://127.0.0.1:8099edge host6 threads β€” replicated for the ingest/mail paths

Loopback-only on both hosts. Callers must run on the same host (or tunnel). LAYA_API_KEY must be set before ever binding a non-loopback interface. systemd user unit laya-serve.service (Restart=always), 4.7 GB RAM, all 3 checkpoints preloaded.

3 Β· Quickstart (60 seconds)

curl -s http://127.0.0.1:8099/v1/systemone -H 'content-type: application/json' -d '{
  "state": "Alert: repeated failed SSH logins from 10.0.0.5 targeting prod-01 root
            over 3 minutes. fail2ban banned the source IP. No successful logins.",
  "questions": {
    "alert_class": {"type": "choice",
      "instructions": "What kind of event is this?",
      "criteria": {
        "bruteforce": "repeated auth failures against a service",
        "malware":    "evidence of malicious code execution",
        "benign":     "routine ops noise, tests, expected maintenance",
        "other":      "anything else"}},
    "urgency":     {"type": "score",
      "instructions": "How urgent is triage for this alert?",
      "criteria": ["not urgent", "soon", "critical"]},
    "auto_close":  {"type": "noul",
      "instructions": "Is this alert safe to auto-close with no analyst action?"}
  }
}'

Round-trip on CPU: ~0.5 s for 3 questions. Response anatomy:

Answer anatomyPOST /v1/systemone β†’ answers"known_pattern": { "type": "noul", ← question type "choice": null, ← noul has no option "noul": 0.0934, ← P(yes) = 9.3% β†’ 91% confident NO "confidence": 0.9066, ← calibrated confidence = THE gate input}"response": { "choice": "auto_remediate", ← picked option (act on this in code) "confidence": 0.0235, ← raw conf (answer_confidence 0.27) β†’ do NOT gate "probabilities": { … } ← full distribution over the bins}routing: {"model":"typed-decisions", "reason":"typed questions"}usage: {"input_tokens": 87}β†’ sanity-check ≀1024Gate rule: conf β‰₯ Ο„ (e.g. 0.7) β†’ act on the choice Β· below β†’ send the turn to gemma :21402 and log both.

Read the confidence first β€” a choice at conf 0.02 is Laya saying "don't trust me"; treat that as "send to gemma", not as an answer. noul works opposite: confidence = 1 βˆ’ noul is how sure it is about the "yes" reading β€” check the field, not the sign convention.

4 Β· Writing a question set (the part that matters)

Each question has 3 ingredients. Get them right and Laya behaves; skip them and you get zero-shot mush (see the real smoke: base-model "malware 0.71" for a textbook bruteforce β€” that's what criteria are for):

  • instructions β€” one sentence, decision-oriented. "Is this a known, recurring event pattern?" beats "classify the event".
  • criteria β€” for choice/score: the options with one-line definitions (score takes an ordered list like ["anytime","soon","critical"]). noul takes none β€” it's binary.
  • a name that says the answer β€” the option is what you act on in code: answers["response"]["choice"] == "auto_remediate".

The live SOC sets (copy these patterns)

# evals/laya_questions.yaml β€” triage (v1)
severity:       type: choice  # informational|low|medium|high|critical, level-based criteria
known_pattern:  type: noul    # "Is this a known, recurring event pattern…?"
response:       type: choice  # note_only|digest_only|email|page|auto_remediate
escalate:       type: noul    # "Should this alert open an incident / reach a human now…"

# email (imap-watcher)
reply_worthy:     type: noul   # "Should the SOC send a substantive human reply…"
priority:         type: score  # anytime|soon|critical
automated_noise:  type: noul   # bounce/notification/bot vs human asking

# ioc (Phase 2 β€” needs a trigger path first)
reputation:        type: choice # malicious|suspicious|neutral|unknown
needs_enrichment:  type: noul

Semantics contract lives in soc-agents/soc-triage/IDENTITY.md; the YAML is the review surface. Keep them in sync β€” there's a self-check test in state_builder.

5 Β· State building β€” the 150-token discipline

Laya reads ≀512 tok (english) / 1024 (typed-decisions) / 8192 (multilingual+max_len). SOC turns are ~120k tok. The whole trick is building a small state β€” never feed raw turns:

# build_alert_state() β€” live code, edge host laya_shadow.py
"Wazuh alert rule 5710 severity level 12 on agent web-01."
"Rule: Multiple failed logins from 1.2.3.4."        # rule description ≀220 chars
"Decoder: sshd. Source IP: 1.2.3.4. Location: /var/log/auth.log."
  • Put the fields the question needs in the state β€” decision inputs only (rule id/level/agent/srcip/description for triage; from/subject/body-head for email).
  • Target ≀150 tokens per alert, ≀300 per email β€” check usage.input_tokens in the response.
  • Nested JSON (rule.level etc.) must be flattened in the builder β€” learned the hard way (Option A).
  • Long docs: HTTP API has no max_len in 0.3.20 β†’ keep HTTP states compact; the SDK supports model="multilingual", max_len=8192 if ever needed.

6 Β· The gate β€” how a decision gets trusted

The whole point of calibrated confidence: conf β‰₯ Ο„ β†’ act on Laya; below β†’ pay for gemma. Everything else is plumbing:

  • choice: gate on answer_confidence; never act on a low-conf auto_remediate (live soak: 115 such rows at conf β‰ˆ0.03 β€” this is why response stays shadowed until finetune).
  • noul: confidence = 1 βˆ’ noul; a tight band (known_pattern sits at 0.68–0.74) means the gate threshold is doing the work.
  • escalate also has an objective cross-check: the deterministic incident rule (sev high/critical). Live agreement 94.6% over 452 decisions.
  • Deterministic facts (severity from rule level) never go to any model β€” a dict lookup is 1.00 accurate and free. Laya's value is the judgment questions around it.

7 Β· Adoption modes (how changes ship safely)

Adoption0 Β· LAYA_MODE=offgemma decides, Laya unused1 Β· shadowLaya logs verdicts, gemma decides2 Β· gatedconf β‰₯ Ο„ β†’ Laya, else gemma3 Β· primaryLaya decides; gemma on low-conf onlyAdoption ladder β€” every part flips per-question, rollback is one env var (LAYA_MODE=off)shadow = live today on ingest+email (561 decisions logged) Β· gated = next (known_pattern + escalate) Β· primary = after 2 clean weeks
  • off β€” default; gemma path untouched. LAYA_MODE=off in /etc/default/<service>.
  • shadow β€” live now: every decision logged to /opt/soc-openclaw/data/laya-shadow.jsonl, caller ignores results. Nothing user-visible changes.
  • gated β€” Laya primary at conf β‰₯ per-question threshold, gemma fallback below. Flip = set LAYA_MODE=gated + thresholds; rollback = the env var.
  • primary β€” after 2 weeks gated-clean. LLM becomes the rare-path escalation only.

8 Β· Checkpoints β€” which one answers

CheckpointCtxPick whenSOC verdict
typed-decisions1024choice/score/noul question setsdefault β€” beats gemma on response (0.588 vs 0.478) at 1/50th latency
english512short english states, binary judgmentscollapses SOC choice questions β€” avoid for triage
multilingual1024β†’8192non-English / advisory-length docsnot needed yet (HTTP max_len unavailable in 0.3.20)

Routing happens per request (routing.model in the response tells you who answered). LAYA_AUTO_TASK=0 β€” routing is explicit, not automatic. One calibration bin (choice:11+) ships uncalibrated β€” confidences from that bin are raw until the Phase 4 re-fit.

9 Β· SDK & CLI (for scripts and experiments)

# Python β€” same shapes as HTTP
from laya import Router
r = Router(preload=True)                    # 4.7 GB RAM, all 3 ckpts
out = r.predict(state, questions)
out = r.predict(long_doc, questions, model="multilingual", max_len=8192)

# CLI β€” quick sanity checks
laya "text" --preset triage --predict    # presets: triage, email, guard, moderation, router

10 Β· Ops: health, watchdog, limits

  • Health: GET /health β†’ {"status":"ok","loaded":[...],"device":"cpu"} β€” bypasses the inference queue, safe to poll.
  • Watchdog already covers it: both hosts probed every 10 min + shadow-log staleness (>6 h = BROKEN).
  • Limits: 64 questions max Β· 50,000 chars state Β· 2 MB body Β· single inference worker (requests queue; don't poll hot).
  • Threads: LAYA_THREADS ≀ physical cores (8 on control host, 6 on edge host) β€” oversubscribing is a big regression.
  • Restart: systemctl --user restart laya-serve (~10 s warm start, preloaded cache).

11 Β· Gotchas (all learned live)

  • PyPI 0.3.20 β‰  GitHub main. Installed API = /v1/systemone + /health only. No /predict, no max_len over HTTP.
  • laya-serve --help boots the server (5 GB RAM, binds nothing useful) β€” config is env-vars only.
  • HF_HUB_OFFLINE=1 works because checkpoints are preloaded β€” preload any new checkpoint manually first.
  • Install order matters: CPU-only torch wheel first, then laya[serve] (saves ~5 GB of CUDA).
  • Zero-shot criteria judgments are mediocre on SOC specifics (documented in LAYA-SETUP.md Β§4) β€” structure is right, accuracy comes from good criteria + the Phase 4 finetune. Don't gate on accuracy before both land.

12 Β· What NOT to use it for

Anything needing generated text: narrations, replies, incident summaries β€” that's gemma's job (Laya can only gate whether narration runs). Also anything with an objective rule (severity from level = dict lookup) β€” spending a model on a deterministic mapping is wasted work you'll have to un-do later.

Sources: docs/LAYA-SETUP.md Β· evals/laya_questions.yaml Β· /opt/soc-openclaw/lib/laya_shadow.py (edge host) Β· docs/laya-baseline.md Β· 452-decision soak, 2026-09-27. Companion: REPORT-LAYA-300-POWER-20260927.html (same server).