View as

Open-source TypeScript agent framework

Build agents you can hold to the record.

An agent framework that compares the model’s claims with what actually ran, during the run. You choose the checks and what happens next.

See the code
  1. It sounds sure. What is it standing on?

    A customer asks about a refund and gets one confident sentence. Fact, or hunch?

  2. A careful person keeps a ledger.

    Which book they opened, why, and what they found. Now anyone can check the work.

  3. An LLM’s tools are its books. Where is its ledger?

    It read two sources, and its answer tells you nothing about them.

  4. Most frameworks log this for you.

    Usually in a dashboard, for your team, after the answer has gone. Guardrails can block an answer, but connecting them to this evidence is your job.

  5. Turn the checks on, and the answer has to stand.

    You choose which checks run. Each one follows the same five steps.

  6. State the rules.

    Tools mark their facts and say what they searched. You say which answer field reports which fact. All before the model sees anything.

    State the rules, so the run can be held to them.
  7. Trace the run.

    FootprintJS, the engine underneath, writes a note at every step and links it to the notes behind it, so you can walk back from any answer. The model’s words go in as claims, never as evidence.

    Trace the run, so there is a record the model did not write.
  8. Assess the claims.

    Code compares what the model said with what the record holds. No model judges it.

  9. Every check is this same comparison.

    Invented values. The wrong time window. An action that never ran. “Not found” stretched past what was searched.

    Assess each claim, so you can see where it differs from that record.
  10. Name the standing.

    One of five words, with its reason. Not a percentage.

  11. Read from the record.

    Never from how sure the model sounded. The saved record gives the same standing later.

    Name the standing, so nobody has to guess how far the answer stands.
  12. Decide what happens.

    Flag it, name the problem back to the model for one more turn, ask the person, or withhold it. A contradiction is never re-asked, because that would invite the model to change the claim, not the facts.

  13. Structured answers wait at the gate.

    A structured answer with your check holds back until the checks finish. Streamed text still goes first, and its standing arrives right after, for your app to show.

  14. Your user can see why.

    What it checked, what it did not, and who vouches for each line. Written from the record, never by a model.

    Decide what happens, because the policy is yours.
  15. The record is not the world.

    A false sentence built from real values can pass. Honest, here, means a claim stays within its evidence, not that it is true.

  16. Each step makes the next one possible.

    Take one away and the answer has less to stand on.

  17. FootprintJS keeps the notes. AgentFootprint holds the model to them.

    It holds claims to the record, not to the world.

    See the code npm install agentfootprint
Skip to the code

What you write

A full agent framework. The checks are a few lines.

You build the agent in AgentFootprint, so it replaces your agent loop. It is not an add-on for another framework. Your tools can stay where they are, over MCP.

Give every answer its standing

One builder call, and it adds nothing to the prompt. A tool that says what it searched gives the standing more to go on.

import { Agent, absent, defineTool } from 'agentfootprint'; const searchBank = defineTool({  name: 'search_bank',  description: 'Search bank settlements',  inputSchema: { type: 'object', properties: {} },  execute: () =>    absent({      what: 'a settlement for this refund',      checked: ['today’s bank file'],      notChecked: ['card network reports'],    }),}); const agent = Agent.create({ provider, model })  .tool(searchBank)  .answerLayer({ standingLine: true })  .build();
The person reads
Your refund went through because the bank settled it.

---

Not sure — a tool said there is ground it did not check or cannot cover; a tool said nothing matched.

Hold a field to a fact

The tool marks the fact, and you say which answer field reports it. A contradiction is withheld before anything streams.

import { Agent, defineTool, describedResult } from 'agentfootprint'; const getRefund = defineTool({  name: 'get_refund',  description: 'Refund status for an order',  inputSchema: { type: 'object', properties: {} },  execute: () =>    describedResult({      facts: [{ entity: 'A-1042', status: 'pending' }],      provenance: {        source: 'refund-ledger',        measuredAt: '2026-01-15T12:00:00Z',      },    }),}); const agent = Agent.create({ provider, model })  .tool(getRefund)  .outputSchema(refundAnswer)  .claims({ status: { entity: 'A-1042', field: 'status' } })  .answerValidation({    id: 'refund',    version: '1',    mode: 'enforce',    agentChecks: ['claims'],    validate: yourOwnCheck,  })  .build();
When the answer says “completed”, run() throws
The answer was withheld: answer validation 'refund' did not pass (failed).

Try an answer check

Same draft. Different evidence.

This runs the real library, with a scripted reply in place of a live model. Your app sets the rules, and the library applies them to that reply.

Scripted reply · no live model
Choose the lookup result

The draft matches a recent lookup for this customer and order.

01 · Lookup evidence

Refund: completed

Customer
customer-42
Order
A-1042
Read at
12:00 UTC

Bank settlement was not checked.

02 · Scripted draft

Refund: completed

For order A-1042, customer customer-42.

A candidate for checking. Showing it here does not mean it was delivered.

03 · App rules

Check before delivery

  • Same customer and order
  • Lookup no older than five minutes
  • Status and time match the lookup

Enforce mode · evidence read only for this caller

Fixed clock: 15 Jan 2026, 12:02 UTC. Nothing is refunded.

Choose a scenario and run it to see what reaches the customer.

This demonstrates configured checks, not model accuracy or independent verification of the refund ledger. Your app owns the rules, trusted sources and fallback wording. Unchecked prose is not covered.

Use answer validation →

Choosing a framework

Four questions to ask of any agent framework.

LangChain, Mastra and others help teams build agent applications, and their guardrails can block an answer. So the useful question is not who has checks. It is what a check can honestly say.

The same four questions work on any framework, this one included.
Ask of any frameworkAgentFootprint
Who judged this answer, code or another model?Code, comparing the answer with the record. No model sets the standing, unless you put one inside your own check.
When a check passes, does it call the answer verified?No. The line reads “This is not a verification.” Only a check you wrote can reach known, and the library cannot see how it decided.
When a search finds nothing, does the answer say what was not searched?It does when the tool declares it. absent() states what was checked and what was not, and the answer reads not sure.
Can the person reading see who vouches for each line?Yes. Explain this answer names the person, library, tool, model or app behind each line, written from the record by fixed templates and never by a model.

A filter that reads only an answer’s words gives the same verdict on the same words. The demo above gives one draft three outcomes, because its check reads the record. Here a tool marks a fact, you declare which field reports it, and the library does the comparison. Anything else is a rule you write.

What other frameworks’ docs show, as of November 2026

LangSmith’s online evaluators score production traces with an LLM judge or your own code. Mastra’s hallucination scorer asks a model and returns a score. NeMo Guardrails’ fact-checking rail returns a score, and its docs say the result depends on the model doing the checking. LangChain and Mastra guardrails can block an answer and can see the run’s messages or tool results, and their guides leave that comparison to you.

This is a difference in emphasis, not a claim of exclusive capabilities or measured superiority. Someone could build any of this elsewhere. Here it is the design.

Questions

Can I keep my framework?

You move to this one. AgentFootprint runs the agent loop, so it replaces your current one. Your tools can stay where they are, over MCP.

How much do I change per tool?

Nothing, to start. To hold a field to a fact, that tool marks the fact with describedResult({ facts }), and the agent adds .outputSchema() and .claims().

What happens by default on a contradiction?

The answer is delivered and the contradiction is recorded. With .answerLayer() the standing reads not sure, and the reason points at the row. List it in an enforcing check and the answer is withheld.

Does it stop a wrong answer in streamed chat?

Only a structured answer with a check is held back before anything streams. Streamed text reaches the person first, and its standing arrives right after, for your app to show.

What share of wrong answers does it catch?

We have no measured rate. It catches what the record can contradict, and a false sentence built from real values can pass.

Would it satisfy legal or audit?

We make no compliance claim. The record shows what ran and what was available. It does not prove correctness or compliance by itself.

Why two packages?

AgentFootprint is built on FootprintJS, which keeps the record and installs with it. Its sibling hcifootprint gives agents a typed map of an app’s real capabilities while the signed-in user stays in control.

Why TypeScript on Node.js?

An agent mostly waits on the network, and Node.js was built for back-end work that waits. The same engine runs in the browser, so the record can reach the person reading the answer. This is a fit, not a claim that it is the best language.