4-Layer pipeline#
Each layer contributes a signal; Analyzer combines them into a single
risk_score (0–100) and a Decision. The order matters — Layer 1 must run
first, Layer 4 must run last, but everything in between is independent.
Call order#
result = analyzer.analyze(MemoryEntry(content="..."))
# 1. _layer1_pattern_matching — regex/keyword
# 2. _semantic_guard.score() — embedding similarity
# 3. _layer2_semantic_analysis — optional LLM
# 4. _transformer.predict() — optional ONNX
# 5. _analyze_internal — combine + apply Layer 3 trust
# 6. _behavioral_baseline.scan — Layer 4 anomaly
# 7. fail_close escalation — final ALLOW → QUARANTINE if degraded
Risk score combination#
The combiner is conservative: it takes the max of layer scores rather
than summing, and applies bounded adjustments from Layer ¾. This prevents
runaway "everything looks slightly suspicious" inputs from reaching block.
risk_score = max(
layer1_pattern_score,
layer1_5_semantic_score * 0.8,
layer2_llm_score,
layer2_ml_transformer_score * 0.85, # only if prob >= 0.92
)
+ layer3_trust_adjustment (range -5 to +30)
+ layer4_baseline_deviation (0, +15, or +30)
Pickle-cached pattern load#
Importing memgar.patterns cold takes ~3500ms because the module defines
770+ Threat dataclasses with multi-line examples. A pickle cache at
~/.cache/memgar/patterns_v1.pkl keyed by the file's SHA-256 drops this to
~3ms on warm starts and is rebuilt automatically when patterns.py changes.
Layer 1.5 health visibility#
When sentence-transformers is not installed or the centroids file is
missing, the layer:
- Logs a single
WARNING("Layer 1.5 disabled: reason …, fix: …"). - Future calls return
score=0.0instantly. health()returns{status: "degraded", reason, fix_hint}.Analyzer.health_check()aggregates the disabled state into the per-layer report so operators can wire it into Prometheus / alerts.
The same pattern is implemented for TransformerDetector and FeedLoader —
no silent zero scoring anywhere in the pipeline.
Layer 2 vs Layer 2-ML#
Both run "semantic" analysis but at different cost / capability points:
| Layer 2 (LLM) | Layer 2-ML (ONNX) | |
|---|---|---|
| Backend | Anthropic Claude | Local DistilBERT/BERT-mini |
| Latency | ~200ms | ~7ms |
| Cost | per-token API charge | free |
| Quality | strongest | good once trained on your domain |
| Failure mode | API outage, rate limit | model absent (graceful) |
| Default | opt-in use_llm=True |
opt-in via artifact presence |
They are complementary: use Layer 2 for borderline cases that Layer 1 + ML flag at the threshold; use Layer 2-ML for every request to keep latency low.
Trust-aware Layer 3#
Source trust is an explicit operator decision — memgar does not auto-learn trust. Register sources at startup:
a = Analyzer()
a.register_source_trust("openai-api", 0.95) # high trust
a.register_source_trust("github-actions", 0.85)
a.register_source_trust("user-form", 0.40) # neutral
a.register_source_trust("untrusted-wiki", 0.10) # low trust
a.register_source_trust("anonymous-input", 0.05) # very low
Borderline risk_score (40-60) gets multiplied by (1 + (1 - trust) * 0.5)
so untrusted sources cross the block threshold more readily. High-trust
sources get a 5-point reduction to absorb minor pattern noise.
Behavioral baseline Layer 4#
Per-agent EMA on scan_risk_score and scan_block_rate:
- After 50 scans the baseline is established.
- Each subsequent scan updates the EMA with α=0.05.
- If the current 5-scan window's risk deviates >2σ from the baseline,
Layer 4 raises
severity=SUSPICIOUS(+15pts). - If >3σ,
severity=CRITICAL(+30pts). - Layer 4 never flags content that scored 0 — it only amplifies existing signal.