---
title: "How do you get AI to read a document and fill in CRM fields reliably?"
blueprint: 002
slug: ai-fields-read-documents
category: AI fields
revised: 2026-09-02
platform: Coevera CRM (formerly Pipeliner CRM)
canonical: https://coevera.webplanet.sk/blueprints/ai-fields-read-documents.html
publisher: Coevera
customer_data: none
---

# How do you get AI to read a document and fill in CRM fields reliably?

**Short answer.** *Recognise once, read many.* Reading a document is by far the most expensive
thing an AI field does, so exactly one field reads the attachments and writes a structured
worksheet — JSON, into a long-text field. Every other field is a cheap text-only reader pointed
at a path inside that worksheet. Twenty extracted values cost one document read plus twenty text
reads, and every value derives from the same reading rather than twenty independent ones that
disagree.

**The constraint that shapes everything else:** a process reads a record snapshot taken when it
starts, so an AI field cannot see what another AI field wrote in the same process. Every
AI-to-AI dependency is therefore a process boundary — one AI pass per process, chained on
completion.

## 01 · The business problem

A reseller buys from suppliers and sells on to end customers. Each deal carries two documents:
the **supplier's quote** (what the goods cost) and the **company's own quote** to the customer
(what they will be sold for). The margin is the difference, sitting in those two PDFs.

In practice nobody worked it out at deal time. The CRM's margin field was pre-filled with a
default percentage, and the real number surfaced weeks later at invoicing. Every pipeline report,
forecast and commission projection in between rested on a guess.

Requirements:

- actual cost of sale and margin, computed from the documents, when the quotes are attached
- a visible audit trail — which document each figure came from, and how it was reached
- an explicit signal when the documents cannot support a reliable answer, rather than a
  confident wrong number
- no new data entry for the sales team

The complications are the interesting part. The two documents rarely line up one-to-one. A
supplier *framework* quote may cover far more capacity than the deal sells, so comparing document
totals gives a wildly wrong margin — the correct comparison scales by quantity actually sold.
Documents carry more than one grand total and the right one is not always the largest. Company
names appear on both documents, so buy side and sell side must be told apart by role, not name.
And a sister company abroad makes an intercompany purchase look like a third-party one.

## 02 · Why the obvious approach fails

Create twenty AI fields, point every one at the record's documents, write a prompt for each,
press *Update Fields*. Four distinct failures, only the first obvious.

**It is twenty document reads, not one.** Reading a PDF is the expensive operation — roughly
**23 s** for a two-document set against about **11 s** for a text-only read. Twenty fields each
opening the same documents multiplies the slowest part of the job by twenty.

**The numbers disagree with each other.** Each field independently re-derives the underlying
figures, and on a non-trivial document set they do not all reach the same answer. Cost of sale
from one field and margin from another end up mutually inconsistent, with no way to tell which is
wrong.

**There is no guaranteed order.** Fields fired from the *Update Fields* button run in no defined
sequence. Anything building on another field's output is a race that works in testing and fails
intermittently forever after.

**A field cannot see what a sibling just wrote.**

> **Platform behaviour.** An automation process reads a **record snapshot taken when it starts**.
> An AI field cannot see what an earlier AI field wrote inside the *same* process — it silently
> reads the previous value. Across processes it works correctly. This is the single biggest
> architectural constraint in the design, and it fails silently: the value read is real, just old.

## 03 · Data model — recognise once, read many

- **A worksheet field** (long text) has documents enabled as a data source. Its prompt tells it to
  return a single JSON object and nothing else. It is the only field that opens a PDF.
- **Reader fields** — one per value you want in a real typed field — have documents *disabled*.
  Each reads the worksheet and extracts one path.
- **Formula fields** handle arithmetic between recognised values. Always live, cannot drift.

> **The layering rule.** A field either reads the *documents* or reads a *worksheet field* —
> never both. A reader left with document access enabled will re-derive values from source and
> contradict the worksheet it was meant to parse. Turn documents off on every reader explicitly.

**Why JSON rather than labelled lines.** There is no JSON field type, but a long-text field whose
prompt says *"return only this JSON object, no prose before or after"* reliably emits valid JSON.
It nests, carries arrays, and a reader can be pointed at a path like `totals.cost_of_sale` rather
than a line prefix. Flat `KEY: value` also works and is fine where already proven; JSON is the
better default for new work.

### Dependency layers

| Layer | Fields | Reads |
|---|---|---|
| L0 | Document classification | the PDFs |
| L1 | Document-set status | L0 |
| L2 | 10 identification readers + the calculation + an independent cross-check | L0 / the documents |
| L3 | The descriptive worksheet | L2 |
| L4 | 3 money readers + 4 descriptive readers | L2, L3 |

The status field at L1 is deliberately alone in its layer. It is the gate deciding whether the
document set is usable at all — keeping it separate stops twelve AI calls being spent on a set
that was never going to produce an answer.

## 04 · Field-level configuration

### What an AI field can and cannot write

| Supported | Not supported |
|---|---|
| `currency`, `email`, `float`, `input` (single-line text), `integer`, `phone`, `text_area`, `url` | `date`, `dropdown`, `checkbox`, `lookup` |

**A recognised date must be captured as text** — there is no way to have an AI field populate a
real date field. **An enum-style value must be a text field** whose prompt constrains output to a
fixed code list, with reporting grouping on the string rather than on dropdown options.

### Writing the JSON schema into a prompt

> **The prompt editor parses prompts as HTML and silently eats anything in angle brackets.**
> Express the schema with literal `""`, `0`, `[]`, `null` — never with `<placeholders>`.
>
> In the reference build a single UI edit destroyed **23 of 26** placeholders in a prompt's output
> contract, leaving bare labels and mangled fragments. Every prose rule survived, so the damage
> was invisible without a diff. Keep prompts in version control outside the platform and diff
> after any UI edit.

### Formula fields for the arithmetic

- **Simple formulas support no function calls at all** — arithmetic over field references and
  literals, nothing more. Function names are rejected at parse time.
- **Advanced formulas do support functions, but the field must be created as a calculated-formula
  field.** Retrofitting an advanced formula onto an existing numeric field is accepted, publishes,
  reads back intact — and never computes, because the field keeps its stored-column nature.
  Create it; do not convert it.
- **Never divide by a recognised value.** An empty numeric field reads as zero with no guard, so a
  percentage formula throws divide-by-zero on every unprocessed record.

### Form membership is functional

> **An AI field can only read a field that is on the entity's edit form.** A field that exists, is
> published, has interface preview enabled and reads perfectly over the API is *invisible* to an
> AI reader if it is not on the form. The reader writes nothing at all — not even the fallback
> value its own prompt specifies.
>
> Proven the hard way: two worksheet fields on the form had all fifteen of their readers working;
> a new worksheet not on the form had all three of its readers write zero on every run.
> Field-by-field configuration diffs showed working and failing readers **identical in every
> attribute**. Adding the field to the form was the entire fix.
>
> **The same applies to calculated fields**, which do not compute at all off-form. A newly added
> formula field also stays empty on every pre-existing record until that record is written again —
> update one field to its own value to force recalculation.

## 05 · Automation & logic

Twelve processes in the reference build — the direct consequence of the snapshot constraint.

**One AI node per dependency layer.** Node completion is the only signal saying "this layer is
finished". Splitting a layer across two chained nodes gives two independent completion signals,
and whichever finishes first triggers downstream too early. Every field in a layer goes into a
single node — one node carries twelve fields, another seven. A correctness rule, not tidiness.

**Chain on completion, not on child nodes.**

> Since AI fields became asynchronous, the AI action node itself carries a trigger-process
> property that fires **on completion of the write**. That is the only safe way to reach anything
> reading what the node wrote. An ordinary *child* node no longer waits: it fires while fields are
> still being written and the downstream process reads the previous value, silently. The run log
> shows the distinction — the node is first *scheduled*, then successful with a field count, and
> only then does the trigger line appear.

**A process is a single linear path.** Conditions may branch, but once inside an action chain you
cannot narrow it again, and an action node may have exactly one child — so there is no fan-out
from an action either.

> **The trap:** a condition's second branch is stored, validated, reads back byte-identical and
> reports healthy — and is *never executed*. Not skipped: never evaluated, with no line in the run
> log at all. Anything conditional past the first action must become another process reached by a
> trigger node.

Where one completion must reach several downstream processes, it triggers a **router** — a
process whose only nodes are a chain of trigger nodes. Each sub-process gates itself, and a false
gate stops that process, so chaining self-gating processes in series works. Order matters when a
later one reads what an earlier one wrote.

**The first node of any process must be a filter node.** An action at the root stores
successfully, reports healthy, and renders an empty canvas with no error.

### Prompt techniques that measurably mattered

- **Check the arithmetic in your worked examples.** A margin percentage was wrong for weeks
  because the example inside the prompt stated a subtly incorrect result. The model was faithfully
  copying a bad example, not rounding carelessly.
- **A self-check must force an independently derived number.** "Multiply the percentage back and
  compare" worked. "Sum the list and compare to the total" was satisfied by writing the target
  twice, concealing two real errors.
- **Replace judgement with mechanical triggers.** A confidence rating described in prose was
  awarded inconsistently; deriving it from an explicit table over the flag list made it reliable.
- **Embed the observed failure as a counter-example.** Every fix that stuck quotes the actual
  wrong output it was written to prevent.
- **Make the model print its reasoning where you need to audit it.** An intermediate breakdown
  block turned a number that varied irreproducibly between runs into one diagnosable within a
  single run.
- **Tolerances on ill-conditioned checks must be proportional.** A fixed absolute tolerance on a
  check multiplying the difference of two nearly-equal percentages produced false disagreement
  verdicts on provably exact deals.

## 06 · Limits & trade-offs

### The failure modes all report success

| Symptom | Actual cause | How to tell |
|---|---|---|
| Node logs success, *updated 0 fields* | AI credit exhaustion | Bites at the **tail** of a chain — earlier nodes spent the last credits. Reads exactly like "the last step is broken". |
| Node logs success, *updated 0 fields* | A field the prompt names is not on the form | Other AI nodes in the same run wrote successfully. |
| Reader returns a plausible but stale value | Same-process snapshot, or a child node that did not wait for an async write | The value is a real previous value, not an error. |
| Branch never runs, no error | A condition's second branch — stored, validated, never evaluated | No evaluation line for it in the run log at all. |
| Formula field permanently empty | Not on the form, or an advanced formula retrofitted rather than created | Config diff against a working formula field shows them identical. |
| Field reported missing right after a run | Read taken before the last write settled | The same read moments later returns the value. |

### The blocker that shaped the whole project

> **AI Smart Fields originally ran synchronously and held the database while they ran.** A full
> recognition pass locked the space for two to five minutes. On a shared space with a dozen users
> that is not deployable, and the build was held back from production for exactly that reason — it
> was correct and unusable at the same time.
>
> This was **resolved by the asynchronous release**, and the chain was reworked onto completion
> triggers the day it shipped. Worth recording rather than quietly deleting: it is why the
> architecture is shaped the way it is, and re-running both test cases after the rework produced
> figures identical to the synchronous originals — which is how you know plumbing changed and
> nothing else did.

### Other constraints hit in this build

- **Batch operations cap at 100 records.**
- **Advanced formulas cannot be validated through the API** — invented function names are accepted
  without complaint, and calculated values are not returned by a normal record read. Trust only
  the formula editor in the UI.
- **Execution is admin-gated.** A personal access token can configure everything and run nothing;
  manual process execution requires a real user.
- **Space-scoped processes are read-only over the API.** Updates are refused with a permissions
  error, so process work needs a window where they are personal-scoped.
- **Renaming a field does not rename it on the form** — the form stores its own copy of each label.
- **Document file-type, size and volume limits are not published.** Verify empirically.
- **Asynchronous is marginally slower end to end** — around 3½ minutes against 2–3 synchronous for
  the same chain, because each handoff waits for a completion event. It no longer holds the space,
  which was the entire point.

### The cost/detail trade-off

Of the 22 AI fields in the reference build, only six are load-bearing — the status, two money
totals, the margin percentage, the confidence rating and the flag list. The other 16 are
descriptive readers populating the record for humans. Trimming them roughly halves run time, at
the cost of recognised detail on the record.

## 07 · Verification

> **The run log is the only honest account of what executed.** Configuration read-backs, success
> statuses and health indicators all lie in the specific ways catalogued in §6. Read the process
> activity log after every run.

- **Field count per node, every run.** A node that should write twelve fields must log twelve.
  This is the check that matters most: a field that silently fails to write keeps its *previous,
  correct* value, so a run against an uncleared record can look perfect while being stale.
- **Clear and re-run from empty.** Every AI field set to null before a full pass, so no value can
  be inherited from an earlier run.
- **Two contrasting document sets end to end** — a straightforward one-to-one pair and a framework
  pair where the supplier quote covers far more than the deal sells. The second proves the design,
  because comparing document totals gives a badly wrong answer there.
- **Every reader compared against its own source worksheet**, not merely checked for plausibility.
- **An independent cross-check field** reading the documents directly and deriving the same figure
  by a different route, with the delta surfaced on the record. Two independently derived numbers
  agreeing is evidence; one number looking sensible is not.
- **Trigger ordering confirmed from the log** — that the router fired its targets in the order a
  downstream gate depends on.
- **Re-run after the async rework and compared to the synchronous fixtures.** Identical figures
  proved the plumbing changed and the logic did not.

**What would signal a regression:** a node logging fewer fields than its layer contains; a reader
returning a value contradicting the worksheet it reads; the cross-check delta widening;
recognition producing confident output on a document set that should have been rejected.

---

Published by Coevera. Abstracted to the reusable pattern — no customer names, no client data, no
personal data.
