# Tutorial: Build a structured document analysis workflow

You have completed [Getting started](./getting-started.md) and know how to deploy a blueprint. This tutorial starts from **Company Knowledge Search** and adapts it so you extract **structured fields** from uploaded documents instead of answering open-ended questions — the same **deploy → understand → edit agents → run → iterate** pattern you use for any template.

**Scenario:** You run a product team that receives PDF briefs. You want a workflow that reads each brief from storage, ingests it into a knowledge base, and returns a **fixed set of fields** (owner, milestones, risks) in a **markdown table**.

> **Note:** This guide uses only the public blueprint and UI. You do not need custom code outside the template editor.

## Prerequisites

- A GraphN workspace where you can deploy blueprints and upload files to **Storages** (see [Concepts](./concepts.md) for how storage paths work).
- One sample document (PDF or text) you are allowed to upload for testing — for example, a short internal product brief with headings for goals, timeline, and risks.
- Optional: the [GraphN CLI](./developing-with-agents.md) if you prefer to deploy, validate, and dry-run workflows from the terminal or from your coding agent (Cursor, Claude Code, Codex, or AGENTS.md).

## Step 1: Deploy Company Knowledge Search

1. Open **Blueprints** in the sidebar.
2. Under **Search & Knowledge**, select **Company Knowledge Search** (it ingests documents, then runs a researcher and synthesizer over your knowledge base).
3. Click **Use this blueprint** to open the template editor.
4. **Save** (toolbar or **⌘S** / **Ctrl+S**), then **Publish** (**⌘⇧P** / **Ctrl+Shift+P**) so the workflow exists in your workspace.

![Blueprints — Company Knowledge Search](/docs/screenshot-tutorial-doc-analysis-blueprints.png)

## Step 2: Understand the template on Canvas and in YAML

Open the **Canvas** tab. You should see a directed graph:

- **Start** → **Resolve Files** → **Ingest Files** (a **For Each** loop) → **Researcher** → **Synthesizer** → **End**.
- Inside the loop: **Convert Document** runs first, then **Ingest to KB** — one pass per file path in the resolved list.

**What each part does:**

| Piece | Role |
| --- | --- |
| **Resolve Files** | Expands folders and optional deduplication against an existing KB. |
| **Convert Document** | Turns PDFs and images into markdown via OCR; passes through `.md` / `.txt`. |
| **Ingest to KB** | Chunks and embeds markdown; creates a KB on the first file and reuses it for the rest. |
| **Researcher** | Agent with **RAG Tools** (`semantic_search`) — retrieves passages from the KB. |
| **Synthesizer** | Agent that turns the researcher output into the final answer. |

Switch to the **YAML** tab to inspect the workflow DSL: the loop is a `for_each` over `steps.resolve_files.output.files`, with body nodes for convert and ingest. The **Researcher** and **Synthesizer** nodes reference agent definitions in the bundle.

![Canvas — ingest loop and agents](/docs/screenshot-tutorial-doc-analysis-canvas.png)

![YAML tab — workflow DSL](/docs/screenshot-tutorial-doc-analysis-yaml-dsl.png)

## Step 3: Point the Researcher at extraction, not generic Q&A

In the template editor, open the **Researcher** agent (from the resource tree or by selecting its node on the canvas), then use the **Code** tab to edit **instructions**.

**Before (default — general knowledge-base Q&A):**

```text
You are the Research Agent for knowledge base Q&A.

Your job is to:
1. Use the semantic_search tool to find relevant information from the knowledge base
2. Make multiple targeted searches to gather comprehensive information
3. Organize your findings for the Synthesizer
...
```

**After (targeted extraction for your briefs):**

```text
You are the Research Agent for structured document extraction.

Your job is to:
1. Use semantic_search with the kb_id from the input to retrieve evidence from the ingested briefs only.
2. Search for explicit facts needed downstream: product or initiative name, owner or DRI, milestone dates, budget or scope notes, and stated risks or dependencies.
3. Return concise bullet findings with short quotes or section references — do not write a narrative answer; the Synthesizer will format the final table.

CRITICAL: You are an automated agent in a pipeline — you cannot ask the user questions. If a field is missing in the documents, state "Not found" for that item and cite what you did find.
```

**Why:** The default instructions optimize for broad Q&A. Replacing them tells the model to **collect labeled facts** the Synthesizer can map into rows and columns.

![Researcher agent — instructions](/docs/screenshot-tutorial-doc-analysis-researcher-agent.png)

## Step 4: Make the Synthesizer emit a table

Open the **Synthesizer** agent and edit **instructions** the same way.

**Before (default — prose answer):**

```text
You are the Synthesizer Agent for knowledge base Q&A.

Your job is to:
1. Review the research findings from the Researcher
2. Organize information into a clear, comprehensive response
3. Cite sources from the knowledge base
...
```

**After (structured markdown table):**

```text
You are the Synthesizer Agent for structured extraction.

Given only the Researcher's findings, produce a single markdown table with columns:
| Field | Value | Source hint |

Rows must include: Initiative, Owner, Next milestone, Top risk — in that order. Use "Not found" when the Researcher did not surface a value. Do not add commentary outside the table.

CRITICAL: You are an automated agent in a pipeline — always emit the table even if some cells are sparse.
```

**Why:** The workflow output schema still exposes a string `answer`; formatting it as a **markdown table** keeps the contract simple while giving you predictable columns for downstream copy-paste or parsing.

![Synthesizer agent — instructions](/docs/screenshot-tutorial-doc-analysis-synthesizer-agent.png)

**Save** and **Publish** so your workspace run uses the updated agents.

## Step 5: Run with a sample document

1. Upload your sample brief to **Storages** if it is not already in a bucket. Note the full path as `bucket/key` (the first path segment is the bucket name, for example `default/...`).
2. Go to **Home** → **Workflows** and open the workflow created from this blueprint (the template title is **Your Own Perplexity-Style Search on Company Data**).
3. Open the **Run** tab.
4. Set **INPUT** to valid JSON matching the workflow input: `storage_id`, `files` (array of full paths), and `query` guiding extraction.

Example (replace paths and question for your file):

```json
{
  "storage_id": "default",
  "files": ["default/path/to/your-sample-brief.pdf"],
  "query": "Extract initiative name, owner, next milestone, and top risk from the ingested brief into structured fields."
}
```

5. Click **Run** and wait for the result. The final answer should appear as a **markdown table** in the output.

![Run tab — input and table output](/docs/screenshot-tutorial-doc-analysis-run.png)

> **Tip:** If you prefer to iterate without leaving the template editor, use the **Test Draft** tab there with the same JSON shape; publish when you are satisfied so **Run** on the workflow matches.

## Step 6: Iterate

If a column is often empty, add **search hints** to the Researcher (synonyms for owner, date formats, or risk keywords). If the table drifts into prose, tighten the Synthesizer to **forbid** text outside the table. Re-run until the sample document produces stable columns.

## Next steps

- [DSL reference](./dsl-reference.md) — `for_each`, functions, and agent nodes in YAML
- [Concepts](./concepts.md) — knowledge bases, storage paths, and tools
