> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lasscyber.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Agnes Analyzer

> Deep dive into the hero endpoint — POST /api/v1/analyze/ — its policy schema, execution semantics, and termination rules.

The Agnes Analyzer (also called the **combined analyzer** or
**execution engine**) is the single endpoint most production
integrations should call:

```
POST /api/v1/analyze/
```

It runs a customer-defined **policy** that tells Agnes which analyzers to
run, in what order, and what to do with their output. This page is the
spec for that policy, with worked examples drawn from the shipped
`default-inbound` policy.

If you only need a working example, the
[Quickstart](/get-started/quickstart) and the SDK
[`PolicyBuilder`](/sdks/python#build-policies-in-code) get you there
fastest.

***

## The endpoint

|            |                                                                                                                  |
| ---------- | ---------------------------------------------------------------------------------------------------------------- |
| **Method** | `POST`                                                                                                           |
| **Path**   | `/api/v1/analyze/`                                                                                               |
| **Auth**   | `Authorization: Bearer ak_…`                                                                                     |
| **Body**   | `{ "prompt": "...", "policy_slug": "...", "policy_id": "...", "sdp_policy_id": "...", "yara_policy_id": "..." }` |

Either `policy_slug` or `policy_id` selects the policy. If neither is
set, the tenant's `is_default` inbound policy runs. The optional
`sdp_policy_id` and `yara_policy_id` overrides let a single policy
template work against multiple SDP / YARA rule sets.

The response carries the per-analyzer outputs, aggregated metrics, the
overall status, and the request ID. See
[Interpreting results](/threat-analysis/interpreting-results) for the
shape.

## What a policy is

A policy is a JSON document with five top-level fields:

| Field                    | Type           | Purpose                                                             |
| ------------------------ | -------------- | ------------------------------------------------------------------- |
| `name`                   | string         | Human-readable display name.                                        |
| `slug`                   | string         | Stable identifier for SDK lookups (e.g. `default-inbound`).         |
| `available_analyzers`    | array          | Which analyzers may run, with parameter values.                     |
| `execution_plan`         | array of steps | Ordered list of steps. Each step is `sequential` or `asynchronous`. |
| `termination_conditions` | array          | Per-analyzer rules for when to short-circuit.                       |

Policies live in Postgres (table-backed `PolicyOrm`) and are tenant-scoped.
Built-in policies (`default-inbound`, `default-outbound`,
`default-permissive`) ship as fixtures and are cloned into a new tenant
on first use.

## Walking through `default-inbound`

The shipped inbound default is the canonical example. Annotated:

```json theme={null}
{
  "name": "Default Inbound",
  "slug": "default-inbound",
  "description": "Strong default protection for user-supplied input...",
  "is_default": true,

  "available_analyzers": [
    {
      "name": "adversarial_detection_analyzer",
      "params": { "model_id": "meta-llama/Llama-Prompt-Guard-2-22M" }
    },
    {
      "name": "safety_moderation_analyzer",
      "params": { "model_id": "google/shieldgemma-2b" }
    },
    { "name": "dlp_analyzer",  "params": {} },
    { "name": "url_analyzer",  "params": {} },
    { "name": "yara_analyzer", "params": {} }
  ],

  "execution_plan": [
    { "type": "sequential",   "analyzers": ["adversarial_detection_analyzer"] },
    { "type": "sequential",   "analyzers": ["safety_moderation_analyzer"] },
    { "type": "asynchronous", "analyzers": ["dlp_analyzer", "url_analyzer", "yara_analyzer"] }
  ],

  "termination_conditions": [
    {
      "analyzer_name": "adversarial_detection_analyzer",
      "output_match": "INJECTION/JAILBREAK",
      "thresholds": [
        { "metric_name": "score", "operator": ">=", "value": 0.85,
          "action_on_met": "terminate_immediately" }
      ],
      "logical_operator": "AND",
      "on_match_action": "terminate_immediately"
    },
    {
      "analyzer_name": "safety_moderation_analyzer",
      "output_match": "UNSAFE",
      "on_match_action": "terminate_immediately"
    },
    {
      "analyzer_name": "dlp_analyzer",
      "thresholds": [
        { "metric_name": "findings_count", "operator": ">", "value": 0,
          "action_on_met": "terminate_immediately" }
      ],
      "on_match_action": "proceed_to_next_step"
    },
    {
      "analyzer_name": "url_analyzer",
      "thresholds": [
        { "metric_name": "unsafe_urls_count", "operator": ">", "value": 0,
          "action_on_met": "terminate_immediately" }
      ],
      "on_match_action": "proceed_to_next_step"
    },
    {
      "analyzer_name": "yara_analyzer",
      "thresholds": [
        { "metric_name": "matches_found", "operator": ">", "value": 0,
          "action_on_met": "terminate_immediately" }
      ],
      "on_match_action": "proceed_to_next_step"
    }
  ],

  "default_telemetry": true
}
```

What that means at runtime:

1. **Step 1** runs the prompt-injection classifier in isolation. If the
   model labels the input `INJECTION/JAILBREAK` *and* the score is
   `>= 0.85`, the run terminates immediately with a "blocked" decision.
2. **Step 2** runs the safety judge. Any unsafe verdict terminates the
   run.
3. **Step 3** runs SDP, URL risk, and YARA in parallel. Any one of
   them — even the cheapest YARA hit — terminates the run.

The first two steps are where you spend GPU time, so they run
sequentially with explicit early-exit. The cheap analyzers in step 3
race against each other so the latency floor is the slowest of the
three rather than their sum.

## Execution semantics

### Steps

Each step has a `type`:

* `sequential` — runs the analyzers in the listed order. The first
  analyzer that errors stops the step with `overall_status = "ERROR"`.
* `asynchronous` — runs the analyzers concurrently with `asyncio.gather`.
  Every analyzer in the group runs to completion (or error) before the
  step finishes.

### Termination rules

After each successful analyzer output, the engine evaluates that
analyzer's termination rules. A rule has up to three signals:

* `output_match` — a regex against the structured output of the
  analyzer (e.g. `INJECTION/JAILBREAK`, `Toxic`, `MALWARE`,
  `EMAIL_ADDRESS`).
* `thresholds` — a comparison on a metric (`>`, `>=`, `==`, `<`, `<=`)
  with a constant value.
* `logical_operator` — `AND` or `OR` connecting `output_match` with the
  thresholds.

Each rule has an `on_match_action`:

* `terminate_immediately` — stop the pipeline; the run's
  `overall_status` becomes `TERMINATED_EARLY`. The decision returned to
  your code is "blocked".
* `proceed_to_next_step` — continue, but flag the analyzer.

### Errors

`InfrastructureAnalyzerError` (raised when an upstream is unhealthy,
e.g. the model service is unreachable) bubbles up as
`analyzer_unavailable` (HTTP 503) with `Retry-After`. SDKs retry these
automatically with exponential backoff.

Other analyzer errors mark that analyzer's status `ERROR` and the
overall run as `ERROR`. The response still includes per-analyzer
details so you can see which analyzer failed.

## Building a policy

You have three ways to author a policy:

### 1. Dashboard policy editor

The schema-driven editor at
[`agnes.lasscyber.com/protection/policies`](https://agnes.lasscyber.com/protection/policies)
walks the analyzer catalog and generates the right inputs for each
analyzer's parameters, metrics, and termination rules. This is the
recommended path for non-engineers.

### 2. SDK `PolicyBuilder`

Both SDKs ship a fluent builder that translates canonical
SDK-facing analyzer names to the server keys at `build()` time:

<CodeGroup>
  ```python Python theme={null}
  from agnes import Agnes, PolicyBuilder

  policy = (
      PolicyBuilder("inbound-strict", slug="inbound-strict")
      .prompt_injection_jailbreak(threshold=0.85)
      .safe_responsible_ai(block_on=["harassment", "self_harm"])
      .sensitive_data(sdp_policy="default-pii")
      .url_risk()
      .yara()
      .terminate_on_any_block()
      .build()
  )

  agnes = Agnes()
  agnes.policies.create(policy)
  ```

  ```typescript TypeScript theme={null}
  import { Agnes, PolicyBuilder } from "@lasscyber/agnes-security";

  const policy = new PolicyBuilder("inbound-strict", { slug: "inbound-strict" })
    .promptInjectionJailbreak({ threshold: 0.85 })
    .safeResponsibleAI({ blockOn: ["harassment", "self_harm"] })
    .sensitiveData({ sdpPolicy: "default-pii" })
    .urlRisk()
    .yara()
    .terminateOnAnyBlock()
    .build();

  const agnes = new Agnes();
  await agnes.policies.create(policy);
  ```
</CodeGroup>

### 3. Raw JSON via API

For automation pipelines, `POST /api/v1/policies/` accepts the
`MultiAnalyzerConfig` shape directly. See the auto-generated
[API reference](/api-reference/overview) for the schema.

## Direction: inbound vs outbound

Agnes does not enforce a particular direction; you decide which policy
runs on inputs versus outputs. The shipped defaults are:

* `default-inbound` — strict, blocks on adversarial / unsafe / sensitive
  data findings.
* `default-outbound` — slightly different thresholds, weighted toward
  catching hallucinated PII and unsafe responses.
* `default-permissive` — runs the same analyzers but only flags; never
  terminates. Useful for shadow rollouts.

The `guard()` helper in both SDKs auto-flips `default-inbound` →
`default-outbound` when you call `check_output`. Policies you create
yourself can use any slug naming; the SDK's `flip` heuristic only
fires for the `default-` prefix.

## Performance and cost levers

* **Order steps cheap-to-expensive.** Termination on a YARA hit costs a
  fraction of a millisecond; on a ShieldGemma run it's tens of
  milliseconds.
* **Use the smaller models when latency matters.** `Llama-Prompt-Guard-2-22M`
  is materially faster than the 86M variant; `shieldgemma-2b` is
  cheaper than `9b`. The catalog labels each option with size and
  context limits.
* **Disable analyzers you do not need.** Removing an analyzer from
  `available_analyzers` removes its cost entirely; setting an
  always-`false` threshold leaves the analyzer running but suppresses
  its termination signal.
* **Shadow first, enforce later.** Clone a policy, set every termination
  to `proceed_to_next_step`, and run it for a week. The
  [Analysis log](/threat-analysis/analysis-logs) tells you what *would*
  have blocked. Promote when you have signal.

## Next

* [Analyzers overview](/analyzers/overview) — choose which analyzers to
  enable.
* [Agnes policies](/policies/agnes-policies) — CRUD, fixtures, and
  versioning of stored policies.
* [Interpreting results](/threat-analysis/interpreting-results) — what
  `analyze()` returns, field by field.
