The vocabulary of 2026 settled on a formula: agent = model + harness. The harness is all the infrastructure around the model — the loop, the tools, the state, the context, the guardrails, and the verification. The word comes from software-testing jargon, where the harness is the scaffold that runs the code under controlled conditions. It fits: that is exactly what it is.

The part the writing on the subject explains well is the anatomy. The part almost none of it shows working is the last item on the list: verification.

This is the 16th post on this blog, written by an AI-assisted process with a mandatory human gate. The harness that holds it has nothing exotic about it — a schema, a test suite, a CI job, and a few versioned text files. This post opens the record of what those pieces actually blocked. And it ends with what they did not catch, which is the most useful part.

The yardstick for this post

Only a defect recorded in a versioned file of the repository enters here: the test that failed, the comment that stayed in the code, the lesson noted in the project memory. The repository is private; for that reason, each case brings the passage or the verifiable fact, not a link the reader would be unable to open.

It is the same yardstick I apply to a number from an external source. If it cannot be audited, it does not enter — not even when the data is mine.

What the harness blocked

What the harness blocked
GateWhat it caughtWhat would have gone live
Zod frontmatter contracta tag outside the taxonomy; description at 162 characters out of 160 allowed, twice in a rowa post with meta truncated in Google and an orphan tag with no route
Lighthouse on the Linux runnera CLS of 0.31 that my machine did not reproducelayout jumping on the screen of someone reading on a phone
SEO auditan invented directive in robots.txt: score 100 → 92 on the three pages measuredan SEO drop across the whole site from a 15-line file
Accessibility audita new section closing on h2 before the footer on h4: 100 → 98a heading hierarchy broken for a screen reader
test:ariaa menu item marked on the wrong page — a bug that has already bitten twicenavigation lying about where the reader is
test:tags and test:homea displayed count different from the posts listed; a draft turning into a public routea wrong number in front of the reader and unapproved text live
test:sanitizemalicious HTML embedded in markdown (CWE-79)execution of a third-party script inside the post
Diagram of harness architecture and fail-closed verification pipeline for AI agents
Figure 1: Fail-closed verification pipeline. The cheapest deterministic gate (Zod schema) filters stochastic AI output before any dynamic test or production deployment.

Three of these deserve the whole story.

The cheapest gate is the contract

Every post passes through a schema declared in code before it exists as a page: a title between 10 and 70 characters, a description between 50 and 160, a category within three fixed values, tags required to be present in a versioned file kept apart. A tag outside the list fails the build — the post does not look bad, it is never born. I have already written about the design of this piece in the post about the content contract.

The detail that matters here is a different one: the error that repeats most often on this gate is mine, not the model’s. The description blew past the limit at 162 characters on posts 14 and 15, in consecutive rounds, each one by two characters. A contract that only distrusts the machine would have let both through.

The gate has to run in the environment that publishes

The most instructive case in the archive is a CLS of 0.31 — three times the tolerable limit — that appeared only on the CI’s Linux runner. On my machine, the same commit measured about 0.001.

It was not measurement fluctuation. The fallback font the system chooses while the webfont loads is different on each operating system, and the swap reflowed the entire page on Linux. The fix was to declare a fallback @font-face with size-adjust and ascent-override calculated from the fonts’ real metrics, instead of degrading the typography. The CLS went to zero.

The lesson fits on one line: “it passed on my machine” is a statement about my machine. The gate that counts is the one that runs where the site is actually built.

In a file with a validator, only what the standard defines gets in

When I published the site’s llms.txt, I wanted to announce it in robots.txt with a line of its own, LLM-Content:. It seemed harmless — a text file, one more directive.

The Lighthouse SEO audit dropped the score from 100 to 92 on the three pages measured: for the validator, an unknown directive is a syntax error, not an extension. And the llms.txt proposal does not even provide for any pointer; the file is found by the fixed path, the same way as robots.txt itself.

Today that attempt lives as a comment inside the file, explaining why it did not come back. The repository documents its own scar — which is, itself, a piece of the harness.

Update note (October 23, 2026): this text was published on August 14, 2026. On September 22, 2026 the project moved to local validation (astro check, test:all, Lighthouse) and the CI workflow was left with only workflow_dispatch; there is no longer an automatic job on a PR, nor an X on the PR. The lesson about a non-blocking gate remains valid, but the mechanism described below is the one from the earlier period.

The gate that does not block, and still works

One choice in this project’s CI tends to surprise people: the Lighthouse job runs with continue-on-error. It does not stop the merge on its own.

The reason is an honest one. Shared runners have real variation in CPU and disk between runs; an absolute score threshold becomes a random failure because of the day’s hardware, not because of a site regression. And a gate that fails without cause teaches the team to click “merge anyway” — when that happens, the gate has stopped protecting and has become decoration.

So it was designed for another function: the script exits with an error when the LCP goes past 2500 ms or the CLS goes past 0.1, the job showed up with a visible X on the PR, back when CI ran on PRs, and the decision to proceed was human and conscious. The deterministic gates — contract, sanitization, accessibility, 404, navigation — those are mandatory and block the merge.

The distinction holds for any harness: a gate that measures something with variance informs; a gate that checks an invariant blocks. Swapping the two produces either a loose process or a process everyone learns to route around.

A guardrail that is state, not a reminder

The most recent example is from four days ago. The site’s ad space reserved height in six places with no ad account at all — a dashed rectangle labeled “Ad,” visible to the reader.

The fix was not commenting the code with a <!-- descomentar depois -->. The component came to render nothing while the environment variable is empty. Turning monetization on, on the day the archive justifies it, means filling in the variable in the hosting panel: the six spaces come back on their own, with the anti-CLS reserved height intact.

The difference between the two solutions is where the decision lives. A comment depends on someone remembering; an environment variable is system state. In a process with an agent in the middle, a guardrail hanging on someone’s memory is the first to fall.

What no gate caught

This is where the post becomes genuinely useful.

A CSS collision, twice. An old rule wiped an element’s display and the home card collapsed to zero height. No test flagged it — and it was not supposed to: this project’s automation proves structure and geometry, not finish. The one who saw it was a human looking at the screen.

A false defect and a false relief in the same tool. An automated screenshot photographed the font fallback and invented a defect that did not exist — it had failed to wait for painting to finish. Days later, a missing image in the capture was almost buried by the comfortable explanation (“it’s just the lazy loading”) and it was a real CSS bug. The test that separates the two cases costs a minute; the comfortable explanation costs a defect in production.

True text in the wrong place. An internal work notice stayed public for days on a legal page. No audit fails that: the HTML was valid, the note was honest, and the place was what was wrong.

The harness does not replace the human gate — it changes what the human needs to look at. The machine keeps the repetitive, the objective, and the tedious. What is left for the person is exactly what they do better: judgment and finish.

The same design in a multi-agent system: BRUpgrade

Update note (October 29, 2026): this section incorporates the post about BRUpgrade, published on September 26, 2026, which ceased to exist as its own page.

Transparency: BRUpgrade is a platform developed by the author of this blog to orchestrate AI agents with governance. It enters here because it applies this post’s yardstick to a larger problem: several agents working on the same project.

When several agents collaborate with no rule, the result tends to be circular conversation, token spend with no control, and a task that never reaches a verdict. BRUpgrade organizes the work like a company:

  • Roles with a closed scope. Each agent holds a declared function, such as software architect, data engineer, interface designer, security specialist, or tester, and it accesses only the tools and instructions of its own role. The one who decides the architecture is not the same agent that implements.
  • A model by complexity. A heavy-reasoning task goes to a stronger model; triage and document formatting go to a faster, cheaper one.
  • A council before execution. A complex demand starts with a session in which specialized chairs analyze security, technical feasibility, impact, and cost, and publish minutes.
  • Central human approval. Nothing becomes code execution without the operator’s explicit authorization.
  • An executable test gate. The agent does not close the task by saying “done”: the work passes through automated tests, type checking, or lint, which answer passed or failed.
  • Isolated execution. Each agent works in its own directory, with restricted commands and with no free access to the operating system.
  • A cost record. Each cycle records the input and output tokens, the accumulated cost per project, and the log of the decisions made.

The platform is built in React 19, TypeScript, and Vite, with contracts validated by Zod. The principle is this post’s, at a larger scale: what decides quality is the verification that runs on its own, not the agent’s promise.

How to build yours, with no platform at all

  1. A contract before the generator. A schema that fails the build is worth more than any instruction in the prompt. The prompt asks; the contract requires.
  2. A mechanical gate before an expensive reviewer. What a script can fail should not reach the large model — in the cycle I opened in numbers in this post, a cheap check kept three reviewers from rediscovering the same trivial defect.
  3. Measure in the environment that publishes. The CI’s operating system, font, network, and CPU are not yours.
  4. In a file with a validator, only what the standard defines. The rest becomes a comment.
  5. A guardrail as state, not as a reminder. An environment variable, not a note in the code.
  6. Declare what the gate does not cover. The list of what the automation does not catch is what says where the human eye has to pass.

No item on that list requires an agent platform, a commercial orchestrator, or a vector database. They are a schema, a few test scripts, a CI file, and the discipline of recording — on top of git, which you already have.

The benefit, measured

The benefit of a harness is not the feeling of control. It is the list of errors it blocked before they cost anything: a broken taxonomy, two descriptions over the limit, a CLS three times above the limit, eight SEO points on every page, two accessibility points, a navigation item lying. All of them captured by pieces that run in minutes and cost nothing per month.

And the honest price: none of these gates made me faster. All of them made me harder to break — which, in a process where a machine writes the draft, is the metric that matters.

Sources

  • Definition of agent harness (infrastructure around the model: tools, memory, state, context, and guardrails) and the origin of the term in early 2026, with contested attribution — Agent harness, Wikipedia (encyclopedia), verified on 2026-08-14.
  • Limits of the content contract (title 10–70, description 50–160, closed category, tags validated against their own file) — src/content/post-schema.mjs (internal project record), verified on 2026-08-14.
  • Mandatory PR gates and the explicit decision to keep the Lighthouse job non-blocking, with the justification of runner variance — .github/workflows/content-check.yml (internal project record), verified on 2026-08-14.
  • Targets of LCP < 2500 ms and CLS < 0.1 applied as an execution failure — tests/lighthouse/run-lighthouse.mjs (internal project record), verified on 2026-08-14.
  • Record of the invented directive that dropped SEO from 100 to 92, preserved as a comment in the file itself — public/robots.txt (internal project record), verified on 2026-08-14.
  • The other defects cited (operating-system-dependent CLS, heading order, CSS collisions, a false defect in the screenshot, an internal notice in production) are noted in the project’s versioned memory — MEMORIA-PROJETO.md (internal project record), verified on 2026-08-14.
  • Security and execution control in agent systems, the reference for the section on BRUpgrade — OWASP Top 10 for Large Language Model Applications (security guide), verified on 2026-09-26.
  • Organization chart, Zod contracts, and BRUpgrade’s execution record — the platform’s internal repository (the author’s internal record, with no public link), verified on 2026-09-26.
  • Declared methodology: the Lighthouse scores cited are from runs of this project’s fixed profile (emulated mobile, simulated throttling), local or on CI — they are not field measurements from real users.