---
title: "We Open-Sourced an AI Agent That Vets Freight Carriers Against FMCSA"
description: "agent-for-logistics checks a carrier's authority, insurance, and safety rating in one call and flags the double-brokering pattern before you tender a load. Here's how it's built."
image: "https://foundrysoft.co/api/og?type=article&title=We+Open-Sourced+an+AI+Agent+That+Vets+Freight+Carriers+Against+FMCSA&cat=Tutorial+%2F%2F+Agents&rt=12+min+read&au=Varun+Raj+Manoharan&dt=2026-07-21"
url: "https://foundrysoft.co/blog/open-source-ai-agent-freight-broker-carrier-vetting"
---

Tutorial // Agents 2026-07-21 12 min read

# We Open-Sourced an AI Agent That Vets Freight Carriers Against FMCSA

agent-for-logistics checks a carrier's authority, insurance, and safety rating in one call and flags the double-brokering pattern before you tender a load. Here's how it's built.

![Varun Raj Manoharan](https://foundrysoft.co/images/about/founder.webp)

Varun Raj Manoharan Founder & Principal Engineer

Eve.dev AI Agents Logistics Open Source TypeScript FMCSA

## Key takeaways

-   Double-brokering, a carrier accepting a load and handing it to an unvetted third party, is a fraud pattern the insurance does not respond to, and vetting a carrier by hand means checking authority, insurance, and safety across separate FMCSA lookups.
-   agent-for-logistics is an open-source Eve.dev agent with seven tools that vets a carrier by MC or USDOT number and returns a do_not_tender, verify_before_tender, or no_flags_found verdict, with the exact FMCSA field or regulation behind every flag.
-   Every capability is split into a pure, unit-tested function in agent/lib/ and a thin Zod-schema wrapper in agent/tools/, so risk rules like NO_POWER_UNITS and INSURANCE_UNKNOWN are tested without a model in the loop.
-   The red-flag rules treat missing data as unresolved, not clean, with one deliberate exception: a missing FMCSA safety rating is scored info rather than warning, because most legitimate carriers are unrated and a rating-shaped warning would just teach brokers to ignore warnings.

We build a lot of AI agents for clients, and most of them never see daylight outside a private repo. This one we open-sourced: [`agent-for-logistics`](https://github.com/FoundrySoftHQ/agent-for-logistics), a copilot for freight brokers and 3PL dispatchers built on the [Eve.dev](https://eve.dev/) framework. It runs in your own Vercel project on your own API keys, and the point of it is one line from the README: "Vet a carrier in 15 seconds instead of 10 minutes across four tabs."

This post covers what the agent does, how it's built, and the one design decision worth stealing for your own tools.

## The problem: vetting a carrier is a fraud check, not a lookup

A freight broker's job, reduced to its core, is matching a shipper's load to a carrier who will actually move it. Before tendering a load to a carrier they haven't worked with, a broker is supposed to check it out: active operating authority, the insurance it claims to carry, any FMCSA safety flags, whether it even has trucks. In practice that means opening the FMCSA SAFER lookup, cross-referencing the insurance filing, checking the safety rating, and eyeballing the fleet size: four separate lookups, done under time pressure, often skipped when a load needs to move now.

The fraud pattern this is supposed to catch is called double-brokering: a carrier accepts a load, then hands it off to a second, unvetted carrier without the broker's knowledge. If that freight gets lost, damaged, or stolen, the broker's cargo insurance typically doesn't respond, because the carrier who actually hauled it was never on the paperwork. The tell is often visible right in the FMCSA record (an entity accepting freight with zero power units and zero drivers is not the one driving the truck), but only if someone actually looks.

## What the agent does

The hero workflow is a single message: paste an MC or USDOT number, get back authority status, insurance, safety rating, fleet size, and double-brokering red flags, each one tied to the specific FMCSA field or regulation it came from, so the broker can defend the call to their customer.

Under the hood that's the `vet_carrier` tool, backed by `agent/lib/fmcsa.ts` (the FMCSA QCMobile API client) and `agent/lib/redflags.ts` (the risk rules). I ran it against a synthetic carrier record (zero power units, no insurance figure supplied, no safety rating on file, the exact double-brokering shape the tool exists to catch) through the actual `vetCarrier()` function in the repo:

JSON

Copy

```json
{
  "status": "ok",
  "carrier": {
    "dotNumber": 3312890,
    "legalName": "SILVER RIVER FREIGHT LLC",
    "dbaName": null,
    "location": "Laredo, TX",
    "powerUnits": 0,
    "drivers": 1,
    "safetyRating": null
  },
  "assessment": {
    "verdict": "do_not_tender",
    "flags": [
      {
        "code": "INSURANCE_UNKNOWN",
        "severity": "warning",
        "message": "Insurance on file could not be confirmed. Verify the certificate directly with the insurer before tendering.",
        "basis": "No insurance figure was supplied; absence of data is not evidence of compliance."
      },
      {
        "code": "NO_POWER_UNITS",
        "severity": "critical",
        "message": "Carrier reports zero power units. A carrier with no trucks accepting a load is the classic double-brokering pattern.",
        "basis": "FMCSA totalPowerUnits is 0."
      },
      {
        "code": "SAFETY_RATING_UNKNOWN",
        "severity": "info",
        "message": "No FMCSA safety rating on file. This is normal — FMCSA rates a carrier only after a compliance review, and most carriers are unrated. It is not itself a problem.",
        "basis": "FMCSA safetyRating is absent."
      }
    ]
  },
  "insuranceEvaluatedDollars": null,
  "dataAsOf": "2026-07-21T14:00:00.000Z"
}
```

Three flags, three different severities, and the verdict (`do_not_tender`) is driven entirely by the one `critical` flag: `NO_POWER_UNITS`. The agent's system prompt tells it to lead with the verdict, then explain the flags with their `basis`, never do arithmetic itself, and never describe a degraded lookup as clean. That last rule matters: if FMCSA can't be reached or the web key is missing, `vet_carrier` returns `status: "degraded"` with guidance instead of a verdict, and the instructions forbid the model from softening that into "looks fine."

Carrier vetting isn't the only thing the agent does. Six other tools cover the rest of a broker's day-to-day math and paperwork:

| Ask it | It uses |
| --- | --- |
| "Should I tender to MC-123456?" | `vet_carrier` |
| "What's the all-in RPM on $2,400 for 1,050 miles?" | `rate_per_mile` |
| "Driver sat 6 hours. What's the detention?" | `accessorial_calculator` |
| "Can I make a 9am delivery 780 miles out?" | `transit_estimator` |
| "Convert this 2% quick-pay discount to an annual rate." | `calculate` |
| "Check this rate confirmation before I sign." | `ingest_document` + a skill |
| "Write the detention dispute letter." | `export_document` + a skill |

I ran `rate_per_mile`'s underlying logic on a $2,400 linehaul over 1,050 miles with a $180 fuel surcharge:

JSON

Copy

```json
{
  "linehaulRpm": 2.29,
  "allInRpm": 2.46,
  "totalDollars": 2580,
  "totalFormatted": "$2,580.00"
}
```

`transit_estimator` runs a day-by-day simulation of FMCSA's hours-of-service rules (49 CFR 395) rather than a closed-form division. The 14-hour on-duty window can bind before the 11-hour driving limit does once you add dock dwell time, and a naive `miles / speed / 11` calculation will happily report an appointment as achievable when it legally isn't. On top of the seven tools, four skill files in `agent/skills/` carry longer-form playbooks: double-brokering red flags, Carmack Amendment cargo claims, rate confirmation review, and detention dispute letters.

## How it's built: lib for logic, tools for wiring

The codebase is organized around one rule, stated in `docs/ARCHITECTURE.md`: every capability the agent can call is split into a pure function in `agent/lib/` and a thin wrapper in `agent/tools/` that exposes it to the model via `defineTool` and a Zod schema.

The reason is testability without a model in the loop. Every file under `tests/` imports from `agent/lib/`, never `agent/tools/`. `npm test` runs over a hundred test cases against `estimateTransit`, `assessCarrier`, `evaluate`, `ratePerMile`, `roundAwayFromZero`, and the rest, with plain inputs and plain assertions, and it passes with zero environment variables and zero network calls. A `defineTool` wrapper stays deliberately too thin to need its own tests: parse input with Zod, call the lib function, return the result. Here's the whole of `agent/tools/rate_per_mile.ts`:

TypeScript

Copy

```typescript
import { defineTool } from "eve/tools";
import { z } from "zod";
import { ratePerMile, brokerMargin } from "../lib/pricing.ts";

export default defineTool({
  description:
    "Compute rate per mile and, when both sides of the rate are known, broker margin. Always use this instead of calculating rates yourself.",
  inputSchema: z.object({
    linehaulDollars: z.number().describe("Linehaul rate in dollars"),
    miles: z.number().describe("Loaded miles"),
    fuelSurchargeDollars: z.number().optional(),
    accessorialsDollars: z.number().optional(),
    customerRateDollars: z
      .number()
      .optional()
      .describe("All-in rate billed to the customer, if computing broker margin"),
    carrierRateDollars: z
      .number()
      .optional()
      .describe("All-in rate paid to the carrier, if computing broker margin"),
  }),
  async execute(input) {
    const rate = ratePerMile(input);
    const margin =
      input.customerRateDollars !== undefined &&
      input.carrierRateDollars !== undefined
        ? brokerMargin({
            customerRateDollars: input.customerRateDollars,
            carrierRateDollars: input.carrierRateDollars,
          })
        : null;
    return { rate, margin };
  },
});
```

No business logic lives in that file. Every number the agent states has to come from a tool like this one. `agent/instructions.md` explicitly forbids the model from doing arithmetic in its own head, and everything that touches a dollar figure routes through `agent/lib/money.ts`, which does the math in integer cents rather than floating-point dollars and rounds ties away from zero rather than using `Math.round`'s toward-positive-infinity bias, so a negative credit and its positive twin round to mirror-image cent values.

`vet_carrier` is the one tool that breaks the "schema-plus-one-call" pattern, because it has a decision to make before it can call the lib layer: is `FMCSA_WEB_KEY` even configured. So `agent/tools/vet_carrier.ts` exports a `vetCarrier()` function that takes an injected lookup client, specifically so `tests/vet_carrier.test.ts` can test that branch without a real network call: the same pattern I used above to generate that JSON output myself, by injecting a fake FMCSA response into the real function.

The risk rules themselves live in `agent/lib/redflags.ts`, and they're a flat list of checks, each producing a typed flag:

TypeScript

Copy

```typescript
export type Severity = "critical" | "warning" | "info";

export type RedFlag = {
  code: string;
  severity: Severity;
  message: string;
  /** Why this rule exists — shown to the user so the verdict is defensible. */
  basis: string;
};
```

A sample of the actual rules, with their real codes and severities:

-   `NOT_AUTHORIZED` (critical): FMCSA's `allowedToOperate` flag isn't `Y`.
-   `OUT_OF_SERVICE` (critical): an out-of-service date is present on the record.
-   `NO_ACTIVE_AUTHORITY` (critical): neither common nor contract authority is active.
-   `INSUFFICIENT_INSURANCE` (critical): a supplied coverage figure is below the $750,000 statutory minimum in 49 CFR 387.9.
-   `NO_POWER_UNITS` (critical): `totalPowerUnits` is exactly 0.
-   `UNSATISFACTORY_RATING` (critical): FMCSA's `safetyRating` is `U`.
-   `INSURANCE_UNKNOWN` (warning): no coverage figure was supplied at all.
-   `POWER_UNITS_UNKNOWN` (warning): `totalPowerUnits` is absent from the record.
-   `CONDITIONAL_RATING` (warning): `safetyRating` is `C`.
-   `HOLDS_BROKER_AUTHORITY` (warning): the carrier also holds active broker authority, meaning it's legally allowed to re-broker your freight.
-   `SAFETY_RATING_UNKNOWN` (info): no rating on file at all.

Three outcomes, driven only by the _blocking_ findings: `do_not_tender` if any flag is `critical`, `verify_before_tender` if the worst flag is a `warning`, `no_flags_found` if nothing critical or warning fired. `info` flags ride along in the array without moving the verdict. That's the design decision worth digging into.

## The design decision: absent data is never a pass, except once

The interesting trade-off in this codebase is how it treats the gap between "we checked and it's fine" and "we don't know." Most of the rules resolve that gap in one direction, deliberately: absence of data is never treated as evidence of compliance.

Look at how `INSURANCE_UNKNOWN` is written in `agent/lib/redflags.ts`:

TypeScript

Copy

```typescript
const insurance = opts.insuranceOnFileDollars;
if (insurance === null || insurance === undefined) {
  flags.push({
    code: "INSURANCE_UNKNOWN",
    severity: "warning",
    message:
      "Insurance on file could not be confirmed. Verify the certificate directly with the insurer before tendering.",
    basis:
      "No insurance figure was supplied; absence of data is not evidence of compliance.",
  });
} else if (insurance < STATUTORY_MINIMUM_INSURANCE) {
  flags.push({
    code: "INSUFFICIENT_INSURANCE",
    severity: "critical",
    message: `Insurance on file ($${insurance.toLocaleString("en-US")}) is below the $750,000 statutory minimum for general freight.`,
    basis: "49 CFR 387.9 minimum public liability coverage.",
  });
}
```

Not having an insurance figure and having a bad one are two different flags with two different severities, but neither is silence. I confirmed the branching by running `assessCarrier` against the same carrier with different `insuranceOnFileDollars` values: at `500000` the flags include `INSUFFICIENT_INSURANCE` (critical); at `1000000` neither insurance flag fires; supply nothing and you get `INSURANCE_UNKNOWN` (warning). There's no fourth path where missing data quietly resolves to "assume compliant." The same logic covers fleet size: `POWER_UNITS_UNKNOWN` fires when `totalPowerUnits` is `null`, distinct from `NO_POWER_UNITS`, which fires when the count is confirmed zero. Absent and zero are both bad, but not the same bad, and the code doesn't collapse them into one message.

Then there's the one place the codebase does the opposite, and the comment explaining why is the most interesting six lines in the repo:

TypeScript

Copy

```typescript
// A missing safety rating is NOT treated as a warning. FMCSA only assigns a
// rating after a compliance review, so the majority of entirely legitimate
// carriers are unrated. Warning here would fire on most honest lookups and
// train users to ignore warnings, which costs more safety than it buys.
// It is surfaced at `info`, which does not move the verdict.
if (carrier.safetyRating === null) {
  flags.push({
    code: "SAFETY_RATING_UNKNOWN",
    severity: "info",
    ...
```

FMCSA doesn't rate every carrier: it assigns Satisfactory, Conditional, or Unsatisfactory only after a compliance review, and most small and mid-size carriers have simply never been reviewed. If a missing rating carried the same `warning` severity as missing insurance, `verify_before_tender` would fire on the majority of ordinary lookups, and a broker staring at a warning on nearly every carrier stops reading warnings. That's the exact failure mode the red-flag design exists to prevent. So this one absence gets `info` severity: it still shows up in `flags`, and the agent still has to mention it (the instructions forbid saying "no flags found" in the same breath as listing one), but it doesn't block the tender on its own.

That's a genuine trade-off, not a shortcut. Getting it wrong in either direction has a real cost: too strict and brokers learn to click through the tool; too lenient and an unrated carrier with a real safety problem slides through unremarked. The repo picked "surface it, don't block on it" and wrote down exactly why.

## Deploying it

There's no hosted version of this and no account to create. You deploy it into your own Vercel project. The README leads with a Deploy-to-Vercel button that prompts for four environment variables: an `AI_GATEWAY_API_KEY` from Vercel's AI Gateway (the model is pinned in `agent/agent.ts` to `anthropic/claude-sonnet-5`, so the gateway key just needs routing permission), a free `FMCSA_WEB_KEY` from FMCSA's own developer portal, and an `AGENT_BASIC_AUTH_USER` / `AGENT_BASIC_AUTH_PASSWORD` pair you pick yourself.

That last pair isn't optional in practice, even though the build succeeds without it. `agent/channels/eve.ts` gates every channel route behind HTTP basic auth, and leaving those two blank still deploys: it just rejects every browser request in production. That's a deliberate fail-closed default: eve's placeholder auth refuses to serve unauthenticated traffic rather than quietly running open. A fifth variable, `AGENT_PUBLIC`, removes auth entirely for a public demo, but it's opt-in and left out of the button's prompt list on purpose, so nobody flips it by accident mid-setup.

Running it locally is the same repo, no basic-auth keys required:

Shell

Copy

```bash
git clone https://github.com/FoundrySoftHQ/agent-for-logistics
cd agent-for-logistics
cp .env.example .env   # fill in AI_GATEWAY_API_KEY and FMCSA_WEB_KEY
npm install
npm run dev
```

Node 24 or later is required: the codebase ships plain `.ts` files with no compile step, and `npm test` runs `node --test "tests/**/*.test.ts"` directly against them using Node's native TypeScript support. I used that same mechanism (`node --experimental-strip-types`) to generate the JSON in this post. On an older Node, `eve start` fails to start rather than degrading.

## What it will get wrong

The README has a section called "What it will get wrong," worth repeating rather than glossing over. It's the same discipline that produced the `SAFETY_RATING_UNKNOWN` trade-off above:

-   **No market rate data.** No load boards, no rate indexes, no lane history. Ask it what a lane pays and it says it can't see that.
-   **Insurance is only checked if you supply the figure.** `vet_carrier` reads authority from FMCSA directly, but it never fetches an insurance dollar amount on its own: you have to hand it the number from the certificate, or it flags the coverage as unverified rather than assuming you're covered.
-   **A clean FMCSA record is the floor, not the ceiling.** FMCSA data reflects what a carrier filed. Fraudsters buy dormant authorities with clean histories on purpose. Call the phone number on the FMCSA record, not the one in the email.
-   **The FMCSA response schema is unverified against a live call.** This build ships with no API key of its own, so `agent/lib/fmcsa.ts`'s field mapping has only ever been exercised against a hand-written fixture, not a real QCMobile response. It's written defensively (every field is optional, the raw response is preserved on `carrier.raw`), but if FMCSA's real field names differ from what's coded, values could come back `null` silently instead of erroring. If you run this with your own web key, the highest-value contribution back to the repo is spot-checking a few known carriers against that fixture and filing what's wrong.

That last one is a real gap, disclosed rather than hidden: the whole point of open-sourcing something like this instead of shipping it as a black-box SaaS product.

## Try it

The repo is at [github.com/FoundrySoftHQ/agent-for-logistics](https://github.com/FoundrySoftHQ/agent-for-logistics), MIT licensed. Clone it, run `npm test` (passes with zero environment variables), and read `docs/ARCHITECTURE.md` if you want to add a tool or skill of your own: eve auto-discovers anything you drop into `agent/tools/` or `agent/skills/`, no separate registration step. If you find a live FMCSA response that doesn't match the fixture, that's the single most useful pull request you could send.

#### Related reading

[Why Trusting AI Generated Code Is the Wrong Goal

Trusting AI generated code was never the right goal, and the 4 percent of developers who say they fully trust it prove nothing is broken: the fix is an AI code review process that makes verification cheap instead of asking how much to trust the output.

AI Code Review Developer Trust Code Quality

](https://foundrysoft.co/blog/developers-dont-trust-ai-generated-code)[Five AI Agents, One Bug: When Missing Data Looks Like a Clean Result

We built and shipped five open-source vertical AI agents. Every single one had the same class of defect: absent or unreadable input rendered as a confident, clean answer. Here is what that bug looks like, why tests miss it, and what actually catches it.

AI Agents Testing Open Source

](https://foundrysoft.co/blog/five-ai-agents-one-bug-missing-data-clean-result)[Best Open Weight LLMs for Agents in 2026

A practical look at the best open weight LLMs for agents in 2026, organized by which constraint, cost, latency, or data residency, should actually decide the pick.

Open Weight LLMs AI Agents LLM Comparison

](https://foundrysoft.co/blog/best-open-weight-llms-agents-2026)

#### Next Article

[

Teaching an Agent Our Client Reporting Playbook with Vercel AI SDK v7 Skills

](https://foundrysoft.co/blog/vercel-ai-sdk-v7-agent-skills)

Available for new projects

## Let's build something great.

Have a project in mind? We are an elite software and AI development studio ready to bring your ideas to production. Let's talk about your roadmap.

Start a Project [See our work](https://foundrysoft.co/work)

```json
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "FoundrySoft",
  "url": "https://foundrysoft.co",
  "logo": "https://foundrysoft.co/logo.svg",
  "description": "FoundrySoft builds production-grade software and AI systems for US companies, from an India-based team of senior engineers.",
  "sameAs": [
    "https://github.com/foundrysofthq",
    "https://www.linkedin.com/company/foundrysoft",
    "https://www.instagram.com/foundrysoft/"
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "FoundrySoft",
  "url": "https://foundrysoft.co"
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "We Open-Sourced an AI Agent That Vets Freight Carriers Against FMCSA",
  "description": "agent-for-logistics checks a carrier's authority, insurance, and safety rating in one call and flags the double-brokering pattern before you tender a load. Here's how it's built.",
  "url": "https://foundrysoft.co/blog/open-source-ai-agent-freight-broker-carrier-vetting",
  "mainEntityOfPage": "https://foundrysoft.co/blog/open-source-ai-agent-freight-broker-carrier-vetting",
  "image": [
    "https://foundrysoft.co/images/blog/open-source-ai-agent-freight-broker-carrier-vetting.webp"
  ],
  "datePublished": "2026-07-21",
  "dateModified": "2026-07-21",
  "keywords": "Eve.dev, AI Agents, Logistics, Open Source, TypeScript, FMCSA",
  "author": {
    "@type": "Person",
    "name": "Varun Raj Manoharan"
  },
  "publisher": {
    "@type": "Organization",
    "name": "FoundrySoft",
    "logo": {
      "@type": "ImageObject",
      "url": "https://foundrysoft.co/logo.svg"
    }
  }
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://foundrysoft.co/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://foundrysoft.co/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "We Open-Sourced an AI Agent That Vets Freight Carriers Against FMCSA",
      "item": "https://foundrysoft.co/blog/open-source-ai-agent-freight-broker-carrier-vetting"
    }
  ]
}
```
