---
url: /pdf-agent-stack/guide/architecture.md
description: >-
  The whole picture of PDF Agent Stack and each MCP's responsibility boundary —
  four independent MCPs and the Skills that decide the calling order, the
  four-layer model, declaration vs. conformance vs. validation, assertion
  strength T1/T2/T3, verdicts returned by code while the LLM writes only the
  explanation
---

# Architecture & Responsibilities

PDF Agent Stack is built as **independent MCPs × Skill orchestration**. Each MCP is self-contained with no dependency on the others; orchestrating multiple servers (procedure and knowledge) is the Skills' job.

## The whole picture

```mermaid
graph LR
  AGENT(["AI agent<br>(Claude Code / Desktop, …)"])

  subgraph FAMILY["PDF Agent Stack"]
    direction TB
    subgraph SKILL["Skills — procedure & orchestration"]
      TRUST{{"pdf-trust<br>incoming audit"}}
      PUBLISH{{"pdf-publish<br>delivery pipeline"}}
      READ{{"pdf-read<br>reading pipeline"}}
    end
    subgraph MCP["MCPs — computation & cryptography (each server independent)"]
      SPEC[["pdf-spec<br>canon (norm)"]]
      READER[["pdf-reader<br>substance (fact)"]]
      VERIFY[["pdf-verify<br>judgment"]]
      WRITER[["pdf-writer<br>creation"]]
    end
    SKILL --> MCP
  end

  IN[/"Incoming PDF"/] --> AGENT
  NEED[/"PDF to produce"/] --> AGENT
  DOC[/"PDF to read"/] --> AGENT
  AGENT <--> FAMILY
  FAMILY --> OUT(["Trust / Publish / Read Report<br>verified, deliverable PDFs and content"])

  SPECDOC[("ISO 32000<br>and 16 more documents")] -.-> SPEC
  VERA[("veraPDF")] -.-> VERIFY
```

Shapes indicate the kind of each element (→ [shape legend](/reference/glossary#how-to-read-the-diagrams-shape-legend)).

An agent may call the MCPs directly, or leave the orchestration to a Skill. With the Skills installed, the **call order, how to read the verdicts, and the report format** are all fixed for you.

There are only two external dependencies: pdf-spec needs the corpus of specification PDFs (not bundled, as they may not be redistributed), and pdf-verify delegates PDF/A and PDF/UA verdicts to veraPDF. The reader and the writer depend on nothing outside themselves.

## The four-layer model — which Skill calls which MCP

```mermaid
graph TB
  subgraph Skills["Skill layer (orchestration)"]
    TRUST{{"pdf-trust<br>incoming audit"}}
    PUBLISH{{"pdf-publish<br>verify, then deliver"}}
    READ{{"pdf-read<br>reading pipeline"}}
  end
  subgraph MCPs["MCP layer (independent, self-contained)"]
    SPEC[["pdf-spec-mcp<br>canon (norm)<br>what the spec requires"]]
    READER[["pdf-reader-mcp<br>substance (fact)<br>what is inside"]]
    VERIFY[["pdf-verify-mcp<br>authenticity & conformance (judgment)<br>genuine and up to standard?"]]
    WRITER[["pdf-writer-mcp<br>creation (production)<br>written the way the spec says"]]
  end
  TRUST -.->|orchestrates| VERIFY & READER & SPEC
  PUBLISH -.->|orchestrates| WRITER & READER & VERIFY
  READ -.->|orchestrates| READER
```

| Layer | Server | Returns | Never does |
|---|---|---|---|
| Canon | pdf-spec | Clauses and requirements (shall/should/may) | Never opens the PDF under examination (it reads only its own spec corpus). Never judges conformance |
| Substance | pdf-reader | Observations (text, structure, signature fields) | **Never says pass/fail**. No cryptographic verification |
| Authenticity & conformance | pdf-verify | Verdicts (signatures, tampering, PDF/A, PDF/UA, 4-value policy) | **Never proves the file meets the standard — it can only find where it breaks the rules** |
| Creation | pdf-writer | New and edited PDFs | Never signs. Can write a label but **cannot make the file meet the standard** |

## The boundary rule (one line)

> If it returns compliant / valid / pass-fail against an ISO standard, it belongs to **verify**; if it only returns observations, it belongs to **reader**.

## Declaration, conformance, validation

This distinction runs through the whole of PDF Agent Stack.

* **Declaration** — A label the file wrote about itself. "I am PDF/A" in the metadata. Writing it is not evidence
* **Conformance** — Whether the file actually meets the standard. There is no way to prove it in full; you can only find where it breaks the rules
* **Validation** — What a validator (veraPDF and the like) reports against the checks it implements. A pass means "this inspection did not fail", not "the file conforms to the standard"

That is why the writer's `ensure_pdfa` is a tool that *writes a label*, and the rule across PDF Agent Stack is: whatever you label, you measure with verify's `validate_conformance`.

### A validator's output is a verdict, not evidence

Outside writing on this subject sometimes lists "validator output" and "signature verification" as examples of *evidence* — of what was seen. PDF Agent Stack keeps the two apart.

| | Who produces it | Example |
| --- | --- | --- |
| Observation (evidence) | reader | XMP carries a `pdfaid` declaration. Two incremental updates are present. No revocation information is embedded in the document |
| Verdict | verify | `use_with_caution`, fired by `POL-CAUTION-REVOCATION-UNKNOWN` |

A veraPDF result is not an observation either. "veraPDF reported no violation under this profile" is the observation; turning it into a value against an acceptance profile is what `evaluate_policy` does.

These three lines say different things. Collapse them and only the last survives.

* **Observation**: XMP declares PDF/A and veraPDF reported no violation under that profile
* **Verdict**: this inspection did not fail (`use_with_caution`; whatever the checks do not cover was not measured)
* **Wrong summary**: the file conforms to PDF/A

### What disappears when four values are collapsed into three

`evaluate_policy` returns four values. Mapping them onto yes / no / unknown loses two rows.

| Four values | Collapsed to three | What is lost |
| --- | --- | --- |
| `trust_and_use` | yes | — |
| `use_with_caution` | yes | The use restriction. "Usable with caveats" becomes "usable as is" |
| `human_review_required` | unknown | The difference between "no evidence" and "unwritable criteria". The latter stops reaching a human |
| `reject` | no | — |

The third row is the point. `human_review_required` covers both "a fact needed for judgment is missing" and "the criteria cannot be written". Collapsed into a single unknown, the latter is filed as something that more evidence would resolve, and never reaches review. **Four values cannot be recovered from three.**

## Incoming and produced PDFs both go through verify

Two of the three inputs in the diagram above (an incoming PDF, a PDF to produce) end with a verdict from pdf-verify.

| Input | Skill | Flow | On `reject` |
|---|---|---|---|
| Incoming PDF | [pdf-trust](/skills/pdf-trust) | audit → four-value verdict → use / archive | Not used. The reason goes into the Trust Report |
| PDF to produce | [pdf-publish](/skills/pdf-publish) | write → read-back → verify (quality gate) → delivery | Not delivered. The reason goes into the Publish Report |

The third input — a PDF you only want to read — does not go through verify. [pdf-read](/skills/pdf-read) picks how to read it and writes what was read and what could not be into a Read Report. It decides neither whether to accept nor whether to deliver, so no verdict from verify is needed.

The four-value verdict (trust_and_use / use_with_caution / human_review_required / reject) is returned by `evaluate_policy`'s rule engine. It is deterministic: the same input always gives the same result. The LLM writes only the explanation of that verdict and the recommended actions; it does not change the verdict.

## Assertion strength (T1/T2/T3)

How strongly a verification result may be stated depends on whether the normative text is at hand.

| Tier | Standard | How strongly you may speak |
|---|---|---|
| T1 | ISO 32000-1/-2, ISO 14289 (PDF/UA) | Quote the clause and state it plainly |
| T2 | ISO 19005 (PDF/A) | Say only "veraPDF judged this COMPLIANT" |
| T3 | ETSI PAdES | Report a structural observation — "structure matching B-LT" — never "conforms" |

The prose on this site follows the same rule.
