# Insight Before Prose
I run an intelligence practice where every report passes a grammar canon and a multi-agent quality gate before it ships. The gates catch a lot. They still do not catch everything, and after months of tightening them I have accepted why: the final step is still a language model predicting the next word. A gate downstream of generation can only reject slop after it exists. It cannot make the generator stop producing it.
So I have been restructuring the pipeline around a different rule: every insight exists before any prose does.
The part that already works
The analysis half of my pipeline was never the problem, because it was never freeform. Before a single sentence gets written, graph analysis maps the concept space of a market: the topical clusters, the negative space between them, which bridges would close which gaps, and which of those gaps are worth a client's next dollar. Value-flow models built on the REA ontology (resources, events, agents) show where money and attention actually move. Each recommended action traces to a score it is expected to lift. These operations are algorithmic. Run them twice on the same corpus and you get the same map.
The failure happens at the handoff. All of that structure gets passed to a model with the instruction to write it up, and the writing step is where invention creeps back in. The insight was computed. The sentence about it was predicted.
Name the artifact in the middle
The fix I am building has two parts. The first is an artifact: a distilled brief that captures the computed insights in typed, structured form, produced within about fifteen minutes of intake and shown to the reader before any long-form work starts. It states the gaps, the bridges, the recommended operations, and the scores each one targets, with every claim pointing back to the graph operation that produced it. The reader approves the plan while it is still cheap to change. The long-form report can only argue what this brief already states. If a paragraph in the final document has no ancestor in the brief, that paragraph is invention, and now there is a mechanical way to say so.
The second part is what NVIDIA's Object-Oriented Agents work clarified for me. Their framework models an agent as an ordinary Python class. Methods with real bodies are deterministic code. Methods whose body is a bare ... are completed at runtime by a model, and whatever the model returns must validate against the method's declared return type or it gets rejected and retried. The design principle underneath is the one I care about: move every operation that can be deterministic out of the model's loop, and make the model's remaining contributions pass through typed contracts.
Apply that to writing and the shape of the pipeline changes. Document structure, section order, figure placement, citations, file assembly: deterministic code. The model's job shrinks to filling declared slots, each with a type, each validated on return. A headline is a constrained value, and so is a comparison sentence with its two figures already bound. The program that assembles the document never asks the model what the document should say. The brief already settled that.
Why this is a substrate argument
The previous post in this pathway argued that intent dies at tool boundaries when every layer restates it in its own dialect. This is the same argument aimed at my own deliverables. A report that exists only as finished prose has already lost its structure; you cannot ask it which score a paragraph serves. A report assembled from a typed brief keeps the declaration alive from analysis to page. The conditional logic a client cares about ("if this gap closes, this score moves") stays a typed, checkable object the whole way down, and the same object can drive a dashboard, a weekly delta report, or a simulation, without anyone re-deriving it from paragraphs.
I am now mapping our knowledge structures onto typed classes and contracts of exactly this kind, and running the object-oriented framework on worked client examples to see where the model holds. The next post will report what survived contact.
The Rails answer to a Python question
Every framework in this space is Python: NVIDIA's object-oriented agents, Pydantic AI, BAML, Instructor, DSPy. They all converge on the same loop: ask the model for a value, validate it against a schema, and when validation fails, send the errors back and ask again. Reading them side by side, I realized our Ruby on Rails work already contains this loop, and contains it more natively than any of them, because Rails spent twenty years hardening exactly this problem under a different name: untrusted user input.
That is the reframe that unlocks the whole design. A language model filling a slot in a report is not a collaborator writing prose. It is a form submission. Rails has one canonical lifecycle for form submissions: parameters come in untrusted, they are assigned to a model object, validations run, and the object either persists or comes back with a structured list of everything wrong with it. errors.full_messages was designed to tell a human what to fix, which makes it a ready-made correction channel for a model. The validation-retry loop the Python frameworks each had to invent is the Rails form cycle with the model on the other end of it. We run this in production today: a fifty-line class that asks for schema-shaped output, validates it, and feeds failures back as corrective messages with a bounded retry count.
Two more pieces of the deterministic pipeline turn out to be things Rails always had. Deterministic prose assembly is the view layer: ERB templates and partials rendering from validated objects, which is what every Rails page has been since 2004, where the Python frameworks needed a dedicated template strategy bolted onto the agent loop. And the contract itself persists. A Python type annotation does its work in process memory and vanishes when the run ends. An ActiveRecord model is backed by a migration and a database: every insight is a row, its evidence is an association, its history is queryable, and the brief a client approved in March is still there in September, joined to the actions it produced. The contract layer and the system of record are the same objects.
The part I find genuinely novel is what Ruby's class-level macros do to the authoring surface. Our Rails instrument already declares domain capabilities the way Rails declares validations: one line at the top of a model, embeddable :body, and the capability is installed. Follow that idiom and a slot the model fills becomes a declaration that sits next to the rules that constrain it:
```ruby class Insight < ApplicationRecord belongs_to :evidence_source enum :kind, { gap: 0, bridge: 1, wedge: 2 }
validates :statement, presence: true, length: { maximum: 240 } validates :statement, banned_terms: true # the grammar canon, in the loop
llm_fills :headline, prompt: "One plain declarative sentence.", length: { maximum: 90 } end ```
The grammar canon stops being an audit that runs after the document exists and becomes a validator that runs before a sentence is allowed to persist. Everything downstream, the report, the dashboard, the weekly delta, renders deterministically from rows that have already passed the rules. The Python field is competing to build a better library for typed model calls. The Rails answer is not a library. It is an application shape, where contract, storage, rendering, and background work were already one framework, and the model is the least trusted user filling in forms.