Blueprint 002 · AI fields

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

Two PDFs on a deal — what the supplier charges us, and what we charge the customer. The margin is sitting in those documents. Getting it out consistently, without spending twenty document reads to extract twenty numbers, is an architecture problem before it is a prompt problem.

Revised 2026-09-02 Verified against a live Coevera space Markdown twin ↓

The 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 is derived 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.

01The 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 on the deal is the difference, and it is 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, every forecast and every commission projection in between was built on a guess that nobody had reason to trust.

What the business wanted was simple to state:

  • the actual cost of sale and actual margin, computed from the documents, at the moment the quotes are attached;
  • a visible audit trail — which document each figure came from, and how the calculation 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 actually sells, so comparing document totals produces a wildly wrong margin — the correct comparison scales by the quantity actually being sold. Documents carry more than one grand total, and the right one is not always the largest. Company names appear on both documents, so the buy side and the sell side must be told apart by role rather than by name. And a sister company in another country makes an intercompany purchase look like a third-party one.

02Why the obvious approach fails

The instinct is direct: decide which twenty values you want, create twenty AI fields, point every one of them at the record's documents, write a prompt for each, and press Update Fields. It fails in four distinct ways, and only the first is obvious.

It is twenty document reads, not one

Reading a PDF is the expensive operation — measured at roughly 23 seconds for a two-document set, against about 11 seconds for a text-only read. Twenty fields each opening the same documents multiplies the slowest part of the job by twenty, for information that was already extracted nineteen times.

The numbers disagree with each other

Worse than slow. Each field independently re-derives the underlying figures, and on a non-trivial document set they do not all reach the same answer. You end up with a cost of sale from one field and a margin from another that are not consistent with each other, and no way to tell which is wrong.

There is no guaranteed order

Fields fired from the Update Fields button run in no defined sequence. Any field meant to build on another's output is a race, and it will appear to work on the first test and fail 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 whole design, and it fails silently: the value it reads is a real value, just the old one.

03Data model — recognise once, read many

One field does the reading. Everything else reads that field's output.

  • A worksheet field — long text — has the documents enabled as a data source. Its prompt tells it to return a single JSON object and nothing else. This is the only field that ever 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 from it.
  • Formula fields handle arithmetic between recognised values. They are always live and cannot drift out of step, which a third AI field doing sums would.

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 the 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, it carries arrays, and a reader can be pointed at a path such as totals.cost_of_sale rather than at a line prefix. Flat KEY: value output also works and is fine where already proven, but JSON is the better default for new work.

Dependency layers

Each field belongs to a layer determined by what it needs, not by preference. In the reference build:

LayerFieldsReads
L0Document classificationthe PDFs
L1Document-set statusL0
L210 identification readers + the calculation + an independent cross-checkL0 / the documents
L3The descriptive worksheetL2
L43 money readers + 4 descriptive readersL2, L3

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

04Field-level configuration

What an AI field can and cannot write

The server enumerates its own supported types:

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

Two consequences worth designing around from the start. A recognised date must be captured as text — there is no way to have an AI field populate a real date field. And an enum-style value must be a text field whose prompt constrains the output to a fixed code list, with reporting grouping on the resulting 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, [] and 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 edit made through the UI.

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 a divide-by-zero on every record that has not been processed yet.

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 placed 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 that were on the form had all fifteen of their readers working; a newly created worksheet that was not on the form had all three of its readers write zero on every run. Field-by-field configuration diffs showed the working and failing readers to be identical in every attribute. The entire fix was adding the field to the form.

The same applies to calculated fields, which do not compute at all off-form. And a newly added formula field stays empty on every pre-existing record until that record is written again — update one field to its own value to force recalculation.

05Automation & logic

The reference build runs twelve processes. That is not over-engineering; it is the direct consequence of the snapshot constraint in §2.

One AI node per dependency layer

Node completion is the only signal that says "this layer is finished". Splitting a layer across two chained nodes produces two independent completion signals, and whichever finishes first triggers downstream too early. So every field in a layer goes into a single node — in the reference build one node carries twelve fields and another carries seven. This is a correctness rule, not tidiness.

Chaining 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 now the only safe way to reach anything that reads what the node wrote.

An ordinary child node no longer waits for the AI. A child trigger fires while the fields are still being written, and the downstream process reads the previous value — silently. The run log makes the distinction visible: the node is first reported as scheduled, then as 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 you are 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 has to 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. Because each sub-process gates itself and a false gate stops that process, chaining self-gating processes in series works, and the order is meaningful when a later one reads what an earlier one wrote.

One more structural rule: 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, and concealed 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 that could be diagnosed within a single run.
  • Tolerances on ill-conditioned checks must be proportional. A fixed absolute tolerance on a check that multiplies the difference of two nearly-equal percentages produced false disagreement verdicts on provably exact deals.

06Limits & trade-offs

The failure modes all report success

This is the theme. Almost every way this design goes wrong looks healthy from the outside.

SymptomActual causeHow to tell
Node logs success, updated 0 fields AI credit exhaustion Bites at the tail of a chain, because 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 Configuration 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 for your own document set.
  • 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 that populate the record for humans. Trimming them roughly halves the run time, at the cost of the recognised detail on the record. Worth knowing before assuming the whole chain is necessary.

07Verification

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.

What was actually checked:

  • Field count per node, every run. A node that should write twelve fields must log twelve. This is the check that matters most, because 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 is the one that proves the design, because comparing document totals gives a badly wrong answer there.
  • Every reader compared against its own source worksheet, not just checked for plausibility.
  • An independent cross-check field that reads the documents directly and derives 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 that contradicts the worksheet it reads; the cross-check delta widening; recognition producing confident output on a document set that should have been rejected as unusable.

Published by Coevera · abstracted to the pattern, no client data Blueprint 002 · rev 2026-09-02