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

# Contract guard

> T2 chain · legal — clause extraction on a LOCAL model. The contract never leaves the machine.

> **T2 chain · legal / compliance** — `model: ollama/llama3.1` at the
> envelope means **every** model call in this file runs on your own
> hardware. For a contract, that's not a preference — it's the
> requirement. Belt-and-braces: `schema:` at infer time, `nika:validate`
> after, `nika:assert` as the hard gate.

## The job

Outside counsel takes a week. Pasting the contract into a cloud chatbot
takes your confidentiality with it. This workflow extracts every
risk-bearing clause — verbatim quotes, typed risk levels — locally,
re-validates the extraction, and refuses to write the memo if the data
doesn't hold.

## The shape

```mermaid theme={"system"}
flowchart LR
  contract["contract · nika:read"]:::invoke
  clauses["clauses · typed"]:::infer
  check["check · nika:validate"]:::invoke
  gate["gate · nika:assert"]:::invoke
  memo["memo"]:::infer
  save["save · nika:write"]:::invoke
  contract --> clauses
  clauses --> check
  check --> gate
  clauses --> memo
  gate --> memo
  memo --> save
  classDef infer fill:#5b8cff22,stroke:#5b8cff,color:#5b8cff
  classDef invoke fill:#22d3ee22,stroke:#22d3ee,color:#22d3ee
```

## The file

```yaml t2-contract-guard.nika.yaml theme={"system"}
nika: v1
workflow: contract-guard
description: "Local-model clause extraction → schema gate → risk memo"

model: ollama/llama3.1      # the whole review runs offline · zero cloud

vars:
  contract_path:
    type: string
    required: true
    description: "Path to the contract (markdown or plain text)"

tasks:
  - id: contract
    invoke:
      tool: "nika:read"
      args: { path: "${{ vars.contract_path }}" }

  - id: clauses
    depends_on: [contract]
    infer:
      prompt: |
        Extract every risk-bearing clause from this contract ·
        ${{ tasks.contract.output }}
        Quote each clause verbatim · classify its risk.
      schema:
        type: object
        required: [clauses]
        properties:
          clauses:
            type: array
            items:
              type: object
              required: [quote, type, risk]
              properties:
                quote: { type: string }
                type: { type: string, enum: [liability, termination, ip, payment, data, other] }
                risk: { type: string, enum: [low, medium, high] }

  - id: check
    depends_on: [clauses]
    invoke:
      tool: "nika:validate"
      args:
        data: "${{ tasks.clauses.output }}"
        format: json
        schema:
          type: object
          required: [clauses]
          properties:
            clauses:
              type: array
              minItems: 1

  - id: gate
    depends_on: [check]
    invoke:
      tool: "nika:assert"
      args:
        condition: "${{ tasks.check.output.valid == true }}"
        message: "Clause extraction failed the schema gate — refusing to write the memo"

  - id: memo
    depends_on: [clauses, gate]
    infer:
      prompt: |
        Write a one-page risk memo from these clauses ·
        ${{ tasks.clauses.output.clauses }}
        Order by risk · high first · cite the quoted text.

  - id: save
    depends_on: [memo]
    invoke:
      tool: "nika:write"
      args:
        path: "./legal/risk-memo.md"
        content: "${{ tasks.memo.output }}"
        create_dirs: true

outputs:
  clauses:
    value: ${{ tasks.clauses.output.clauses }}
    type: array
    description: "Typed risk-bearing clauses, verbatim quotes"
  memo: ${{ tasks.memo.output }}
```

## How it works

<Steps>
  <Step title="Sovereignty is one line">
    The envelope `model:` points at Ollama. Same file, same schema, same
    everything — just no cloud. Swap back to a cloud provider for
    non-sensitive documents; the workflow doesn't change shape.
  </Step>

  <Step title="validate re-checks what schema promised">
    `nika:validate` runs the extraction against a second, stricter
    schema (`minItems: 1`) and returns `{valid, errors}` — belt and
    braces for legal-grade output.
  </Step>

  <Step title="assert is the fail-fast guard">
    `when:` skips; `nika:assert` FAILS. A memo built on a bad extraction
    is worse than no memo — so the gate throws, loudly.
  </Step>
</Steps>

## Constructs you just used

| Construct                   | Where             | Reference                        |
| --------------------------- | ----------------- | -------------------------------- |
| local provider (`ollama/…`) | envelope `model:` | [Providers](/concepts/providers) |
| `nika:validate`             | `check`           | [Builtins](/reference/builtins)  |
| `nika:assert` vs `when:`    | `gate`            | [Builtins](/reference/builtins)  |
| verbatim-quote schema       | `clauses`         | [The 4 verbs](/concepts/verbs)   |

## Make it yours

* Compare against your clause playbook: `nika:read` the playbook + a second `infer` that flags deviations.
* Batch a folder of NDAs: `nika:glob` + `for_each` ([Localization factory](/examples/localization-factory) shows the chained fan-out).
* HR version: same shape over candidate agreements or policy docs — sensitive data, local model, typed findings.

<Card title="Level up · T3 fan-out" icon="satellite-dish" href="/examples/competitor-radar">
  Next tier: collections the workflow discovers at runtime, processed in
  parallel with retry and a leash.
</Card>
