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

# Give your agent a knowledge base

> One real MCP session: build a workflow with a Knowledge node, upload a service note, an invoice and a vendor list, then let the agent list, search, read, ask and download the corpus

<Note>
  The [Knowledge](/guides/nodes/knowledge) node is in **alpha** and appears once the feature is switched on for your organization. Ask us to enable it.
</Note>

[Ask your filing cabinet](/examples/ask-your-filing-cabinet) shows the Knowledge node from the app and the REST API. This page is the same idea from an agent's seat: every step is an [MCP](/api-reference-v3/mcp) tool call, and every payload below comes from one live session, with identifiers shortened. An operations team keeps three kinds of document in one workflow: the service note for a piece of equipment, the invoices that come in for it, and the list of approved vendors. The agent builds the workflow, feeds it the files, and then works the corpus with five read tools.

<div className="af-payoff">
  <div className="af-payoff-pane">
    <div className="af-payoff-cap">The agent asks</div>

    <div className="af-payoff-line">
      {'What spares are held on site for the'}
    </div>

    <div className="af-payoff-line">
      {'Torvik Ridge flux stabiliser, and which'}
    </div>

    <div className="af-payoff-line">
      {'vendor would we order cartridges from?'}
    </div>
  </div>

  <div className="af-payoff-arrow">→</div>

  <div className="af-payoff-pane">
    <div className="af-payoff-cap">It gets back</div>

    <div className="af-payoff-line">
      <b>
        {'Two desiccant cartridges, one coolant'}
      </b>
    </div>

    <div className="af-payoff-line">
      <b>
        {'pump, a set of toroid gaskets'}
      </b>

      {', and'}
    </div>

    <div className="af-payoff-line">
      {'"the catalog does not say who supplies'}
    </div>

    <div className="af-payoff-line">
      {'cartridges", with the five vendors listed'}
    </div>

    <div className="af-payoff-line">
      <span className="af-cite">
        {'↳ 7 citations: page and box on the PDF, block on the CSV'}
      </span>
    </div>
  </div>
</div>

<div className="af-cost">
  <span><b>Nodes</b><strong>{'Parse → Knowledge'}</strong></span>
  <span><b>Tools</b><strong>{'10 calls'}</strong>{' to build and feed it, then one per question or lookup'}</span>
  <span><b>Credits</b>{' per page for fresh content, then per question; list, search, read and download are free'}</span>
</div>

The session in order:

1. `create_workflow` builds a parse → knowledge graph.
2. `stage_files` and `upload_documents` put three local files into the workflow.
3. `run_document_packet` and `get_run` parse them; the corpus indexes on its own right after.
4. `ask_knowledge` answers questions with citations, and keeps a thread for follow-ups.
5. `list_knowledge`, `search_knowledge` and `read_knowledge` work the corpus without a model.
6. `download_knowledge` hands over the whole corpus as one archive.

## 1. Create the workflow

The Knowledge node has no options. Its presence opts the workflow into indexing: every completed run adds its parsed documents to the corpus. `mode: "lite"` on the parse is enough for text documents; use `standard` when the pages are scans.

```json theme={null}
// create_workflow
{ "body": {
  "name": "Torvik Ridge site records",
  "description": "Service notes, invoices and the vendor list for one substation",
  "nodes": [
    { "id": "parse_1", "type": "parse", "mode": "lite" },
    { "id": "kb_1", "type": "knowledge" }
  ],
  "edges": [ { "source": "parse_1", "target": "kb_1" } ]
} }
```

```json theme={null}
{ "id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66", "name": "Torvik Ridge site records", "…": "…" }
```

## 2. Stage and upload the files

A tool call carries JSON, not bytes. `stage_files` returns one upload form per file plus a short `object_id` to refer to it by. Send the real byte length as `declared_size`.

```json theme={null}
// stage_files
{ "body": { "files": [
  { "filename": "knowledge_note.pdf",  "declared_size": 2423, "content_type": "application/pdf" },
  { "filename": "single_invoice.pdf",  "declared_size": 2087, "content_type": "application/pdf" },
  { "filename": "vendor_catalog.csv",  "declared_size": 1188, "content_type": "text/csv" }
] } }
```

The agent POSTs each file as multipart/form-data to its slot, every `upload.fields` entry first, then the file. All three answered `204`.

```bash theme={null}
curl -F 'key=…' -F 'policy=…' -F '…=…' -F file=@vendor_catalog.csv "https://s3.eu-west-1.amazonaws.com/…"
```

`upload_documents` then registers the three as one document packet, atomically. A CSV or a spreadsheet goes in exactly like a PDF.

```json theme={null}
// upload_documents
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66",
  "body": { "staged_files": [
    { "object_id": "06aa837e5e16…", "filename": "knowledge_note.pdf" },
    { "object_id": "06aa837e604f…", "filename": "single_invoice.pdf" },
    { "object_id": "06aa837e62a6…", "filename": "vendor_catalog.csv" }
  ] } }
```

```json theme={null}
{ "document_packet_id": "06aa837e-6674-7659-8000-adeb079141b4", "files": [ "…3 files…" ] }
```

## 3. Run it, then let the index catch up

```json theme={null}
// run_document_packet
{ "document_packet_id": "06aa837e-6674-7659-8000-adeb079141b4" }
```

`get_run` with `wait_seconds` long-polls; three small text documents were `processed` in about three seconds. Indexing starts on its own when the run completes and takes a few more seconds. An `ask_knowledge` call in that window is refused with a retryable error, not a wrong answer:

```json theme={null}
{ "error_code": "KNOWLEDGE_NOT_READY", "retryable": true, "status": 409, "request_id": "6aa8383c…" }
```

Retry every few seconds while indexing is in progress. The first two asks of this session got that envelope; the third answered. New runs later re-index in the background while the existing corpus keeps serving, so this wait only happens the first time.

## 4. Ask, and keep the thread

A question that spans two documents, with a `thread_id` so the next question can build on it. Mint the id yourself with the prefix `kb-`.

```json theme={null}
// ask_knowledge
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66",
  "body": {
    "question": "What spares are held on site for the Torvik Ridge flux stabiliser, and which vendor in the catalog would we order more desiccant cartridges from?",
    "thread_id": "kb-torvik-spares"
  } }
```

Forty-seven seconds later:

```json theme={null}
{ "answer": "### Spares Held on Site\nThe following spares are held on site for the Torvik Ridge flux stabiliser (a Halvorsen Meridian unit, part number QX-7731-KAPPA):\n* **Two desiccant cartridges**\n* **One coolant pump**\n* **A set of gaskets for the toroid housing**\n\n### Ordering Desiccant Cartridges\nThe provided knowledge base **does not contain information** regarding which vendor in the catalog we would order desiccant cartridges from.\n\nThe vendor catalog (`2026-09/vendor_catalog.md`) lists five vendors and their contact information but does not include any inventory lists …",
  "citations": [
    { "path": "2026-09/knowledge_note.md",
      "quote": "Spares held on site: two desiccant cartridges, one coolant pump, and a set of gaskets for the toroid housing.",
      "block_id": "p1_b4", "page": 1,
      "bbox": { "x0": 0.1138, "y0": 0.4115, "x1": 0.8243, "y1": 0.4663 } },
    { "path": "2026-09/vendor_catalog.md",
      "quote": "<td>Umbrella Logistics</td>",
      "block_id": "md_p1_b2", "page": null, "bbox": null },
    "…5 more…"
  ],
  "steps_used": 7,
  "thread_id": "kb-torvik-spares" }
```

Two things to read off that answer. The agent said what the corpus does **not** contain instead of guessing a vendor: an answer is served only when every quote it rests on was found in a document. And the citations differ by source: on the PDF each quote resolves to a page and a box in page-relative coordinates, ready to highlight; on the CSV there is no page to point at, so `page` and `bbox` are `null` and the block id locates the row inside the rendered table.

The follow-up carries the same `thread_id` and no restated context:

```json theme={null}
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66",
  "body": { "question": "And what are that vendor's payment terms?", "thread_id": "kb-torvik-spares" } }
```

```json theme={null}
{ "answer": "As the knowledge base does not specify which of the catalog vendors supplies the desiccant cartridges, the specific vendor cannot be identified.\n\nHowever, all five vendors listed in the catalog (`vendor_catalog.md`) share the exact same payment terms: **Net 30**. …",
  "citations": [
    { "path": "2026-09/vendor_catalog.md",
      "quote": "<td>Umbrella Logistics</td>\n<td>ap@umbrellalogistics.example</td>\n<td>Net 30</td>",
      "block_id": "md_p1_b2", "page": null, "bbox": null },
    "…4 more…"
  ],
  "steps_used": 3, "thread_id": "kb-torvik-spares" }
```

Fourteen seconds: the thread remembered which vendor was in question and the agent only had to search for the terms. A question about the invoice, in a fresh thread, answers the issuer, number, total and date with one citation each, every one with a box on page 1:

```json theme={null}
{ "answer": "* **Issuer:** Wayne Enterprises\n* **Invoice Number:** `INV-2026-9001`\n* **Total:** `$4,594.62` (USD)\n* **Issue Date:** `2026-02-20`",
  "citations": [
    { "path": "2026-09/single_invoice.md", "quote": "From: Wayne Enterprises", "block_id": "p1_b5", "page": 1, "bbox": { "…": "…" } },
    { "path": "2026-09/single_invoice.md", "quote": "Invoice #: INV-2026-9001", "block_id": "p1_b1", "page": 1, "bbox": { "…": "…" } },
    { "path": "2026-09/single_invoice.md", "quote": "TOTAL: $4,594.62", "block_id": "p1_b11", "page": 1, "bbox": { "…": "…" } },
    { "path": "2026-09/single_invoice.md", "quote": "Issue date: 2026-02-20", "block_id": "p1_b2", "page": 1, "bbox": { "…": "…" } }
  ],
  "steps_used": 6, "thread_id": null }
```

Every `ask_knowledge` call is billed and runs an agent over the corpus, so it takes seconds to a couple of minutes. The three tools below cost nothing and answer in about a second; reach for them first when the agent already knows what it is looking for.

## 5. List, search and read, without a model

`list_knowledge` returns the corpus tree. Documents are filed by upload month unless a [Classify](/guides/nodes/classify) node runs before Knowledge, in which case the category names the folder. The `.kb/` pages are generated navigation (`kind: "index"`), never evidence.

```json theme={null}
// list_knowledge
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66", "folder": "2026-09" }
```

```json theme={null}
{ "items": [
    { "path": "2026-09/knowledge_note.md", "kind": "document", "folder": "2026-09",
      "title": "Torvik Ridge Substation - Flux Stabiliser Service Note", "page_count": 1 },
    { "path": "2026-09/single_invoice.md", "kind": "document", "folder": "2026-09", "title": "INVOICE", "page_count": 1 },
    { "path": "2026-09/vendor_catalog.md", "kind": "document", "folder": "2026-09", "title": "Sheet: …", "page_count": 0 }
  ],
  "next_cursor": null }
```

Pages of up to 200 entries; pass `next_cursor` back as `cursor` to continue. `search_knowledge` ranks documents for a query with the same index the agent's own search uses. The snippet marks the matched term:

```json theme={null}
// search_knowledge
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66", "query": "desiccant cartridges", "limit": 3 }
```

```json theme={null}
{ "result": [
    { "path": "2026-09/knowledge_note.md", "folder": "2026-09",
      "title": "Torvik Ridge Substation - Flux Stabiliser Service Note", "page_count": 1,
      "snippet": "…a quarterly inspection of the toroid housing, an annual replacement of the «desiccant» cartridges, and a firmware check against the vendor's release notes…",
      "rank": 2.6152 }
] }
```

`read_knowledge` returns one document as the corpus stores it: a frontmatter block, then the parsed markdown with an anchor before every block. Those anchors are the `block_id` values the citations use.

```json theme={null}
// read_knowledge
{ "workflow_id": "06aa837e-2bfe-7e1c-8000-55ae77b75f66", "path": "2026-09/knowledge_note.md" }
```

```json theme={null}
{ "path": "2026-09/knowledge_note.md", "kind": "document", "folder": "2026-09",
  "title": "Torvik Ridge Substation - Flux Stabiliser Service Note", "page_count": 1,
  "content": "---\ntype: \"document\"\ntitle: \"Torvik Ridge Substation - Flux Stabiliser Service Note\"\nsource_file: \"knowledge_note.pdf\"\nfile_id: \"06aa837e5e16…\"\npages: 1\n---\n\n<a id=\"p1_b0\"></a>\n\n# Torvik Ridge Substation - Flux Stabiliser Service Note\n\n<a id=\"p1_b1\"></a>\n\nThe Halvorsen Meridian flux stabiliser installed in bay 4 of the Torvik Ridge substation carries the manufacturer part number QX-7731-KAPPA. …",
  "truncated": false,
  "file_id": "06aa837e5e16…" }
```

A document longer than 20,000 characters comes back cut there with `truncated: true`; `download_knowledge` below is the way to get the whole text. A path that is not in the corpus is a plain `NOT_FOUND`:

```json theme={null}
{ "error": "The resource does not exist.", "detail": "Knowledge entry not found",
  "error_code": "NOT_FOUND", "retryable": false, "status": 404, "request_id": "6aa838ab…" }
```

## 6. Download the corpus

`download_knowledge` takes only the workflow id and returns a presigned URL for the current corpus as one `tar.gz`, valid for 15 minutes. It is the way to hand the whole corpus to something outside anyformat: a local search index, a backup, another agent.

```json theme={null}
{ "corpus_version": "2fdecfca8922…", "url": "https://s3.eu-west-1.amazonaws.com/…", "size_bytes": 4235,
  "document_count": 3, "built_at": "2026-09-14T18:07:49Z", "expires_in_seconds": 900 }
```

The archive holds the same tree `list_knowledge` shows, plus the structure the citations resolve against:

```text theme={null}
.kb/INDEX.md                          navigation the agent reads first
.kb/manifest.json                     every document and block with byte ranges, pages and boxes
.kb/navigation/2026-09/INDEX.md
.kb/views/category/…  .kb/views/date/…
2026-09/knowledge_note.md             the document, as read_knowledge returns it
2026-09/knowledge_note.blocks.json    page and box per block (PDF only)
2026-09/single_invoice.md
2026-09/single_invoice.blocks.json
2026-09/vendor_catalog.md
.meta.json
```

<Warning>
  Possession of the URL is read permission for the corpus until it expires. It survives revoking the API key that requested it, and it can stop answering after a re-index or a deletion. Treat it like the bytes themselves.
</Warning>

## When to use which tool

| The agent wants                         | Tool                 | Model | Billed       |
| --------------------------------------- | -------------------- | ----- | ------------ |
| The answer to a question, with evidence | `ask_knowledge`      | yes   | per question |
| To see what is in the corpus            | `list_knowledge`     | no    | no           |
| The documents about X                   | `search_knowledge`   | no    | no           |
| One document's text                     | `read_knowledge`     | no    | no           |
| Everything, as files                    | `download_knowledge` | no    | no           |

Every read tool answers `KNOWLEDGE_NOT_ENABLED` on a workflow without the node, `KNOWLEDGE_NOT_READY` while the first corpus is still building, and a flat `404` when the caller cannot see the workflow.

## Related

* [Agents over MCP](/guides/mcp): staging, the typed graph, retry-safe runs and reading extraction results.
* [Ask your filing cabinet](/examples/ask-your-filing-cabinet): the same node from the app and the REST API, with the credit model.
* [MCP server reference](/api-reference-v3/mcp): connection setup, scopes and the full tool table.
* [Knowledge endpoints](/api-reference-v3/knowledge/ask): the REST routes behind these tools.
