MIT · ONE DEPENDENCY · TREE-SHAKEABLE

hcifootprint

A context engine for your frontend.

Let an AI agent use your app — safely.

It gives an LLM your app’s own account of where it is, what it can do and what happened — and says so when it does not know. The agent operates your real screens; your rules stay in charge.

The agent works your real screens as the signed-in user, inside the permissions they already have. Nothing new to open up, nothing rewritten. What it does is recorded — and what it could not do is recorded too.

read the docs →
human — recordedagent — recorded, and gated

In one end-to-end example run, the agent typed, was stopped for approval, created, navigated, and configured.

one app
two travellers
two trails
one gate
PAGE A
PAGE B
PAGE C
A → B
B → C

A human interacts, then navigates — every step recorded.The agent takes over — where the human stopped.It fills the form.Agent navigates. Same map, same rules.Agent interacts — recorded, attributed.A consequential step — it stops and asks.Approved. One journey, two travellers.

↓ SCROLL — THE OBJECT FLATTENS INTO THE MAP
FIG. 1 — APP GRAPH01 ROUTE
SCENE 00 / 07
01 — THE MAP

Your app is already a map.

Nothing to rewrite.

Pages are places. Routes are roads. hcifootprint reads the map your app already has — its route table — and draws nothing you did not declare.

The map comes from the route table you already ship. No new backend, no re-architecture, no second source of truth to keep in step with the first.

+ places, + roads
02 — THE JOURNEY

A session is a path, not a log line.

One record of what happened.

A traveller walks. Each step is written as it happens: where it started, where it went, what it touched. The trail is the transcript — there is no second version of events.

Every step is written as it happens, by the app itself. When someone asks what the agent did on a customer’s account, the answer is a record — not a reconstruction from logs after the fact.

+ a trail, + a ledger
03 — WHAT'S POSSIBLE HERE

Arrive somewhere, and only that place's actions exist.

You pay for the page, not the app.

The model is handed the four things it can do here. Not the four hundred it could do somewhere else. Nobody hands a tourist every restaurant in the country.

Only what is possible here is sent. On our own demo that is 6.1× fewer tokens per turn than handing over the page’s markup — measured, with the script in the repo so you can run it against your own app rather than take our word for it.

+ stops at this place
04 — THE BOUNDARY

Your app owns meaning. The library owns mechanism.

No new attack surface.

Rules, validation and routing stay exactly where they are. hcifootprint sits outside them: it observes, it reports, it refuses. Nothing is injected. Remove it and the app is untouched.

The agent acts as the signed-in user, through the buttons and permissions they already have. No new endpoints, no new grants. Remove the library and your app is exactly as it was.

+ a membrane, not a wrapper
05 — GAPS

It cannot do this, so it says so.

You hear it from the app, not the customer.

The agent reaches for something that was never declared. There is no fallback, no improvised click, no cheerful summary. A row appears: what it wanted, why it couldn’t. Honest absence is the feature.

When the agent cannot do something, that is recorded as a gap the moment it happens — with what it wanted and why it failed. The alternative is a confident summary of work nobody did.

+ a second traveller, + a refusal
06 — THE CONFIRM GATE

Consequential steps stop and ask.

An approval you can prove.

Allow, always allow, or prevent — with the receipts attached. What it read. What it intends to write. Observed on screen, never inferred. Answer the card on the map; the journey waits for you.

Before anything consequential, a person sees exactly what will happen and answers. The library will not take the agent’s word that someone approved: the yes has to point at a decision a human actually recorded.

+ a gate, + a decision
07 — BRING YOUR OWN

The map was never hand-drawn.

Adopt it in layers, stop anywhere.

It grew from three things your app already has: the router, the journeys you name, the controls you expose. Three plugs snap in. Everything above was drawn from them.

Start with the routes you already have and get a map in an afternoon. Add actions when you want the agent to act, and the trust layer when it touches something that matters. Each layer stands on its own.

+ three declared sources
THE WHOLE INTEGRATION

Twenty-eight declared lines.

This is a real integration, rewritten against the current library and compiled before it was published here. It reads like a description of the app rather than a description of the library.

Three declared sources, one session, one navigate. There is no wrapper to keep in sync and no generated file to regenerate.

no wrappersno proxiesno injected DOMdelete this file and the app is unchanged
AGENT-MAP.TS28 LINES
01  import { buildNavigationGraph, fromRoutes, fromJourneys, fromLiveStore, type LiveActionStore } from 'hcifootprint';
02  import { ROUTES, router } from './router.js';
03  import { JOURNEYS } from './funnels.js';
04  import { legacyStore } from './actionStore.js';
05  
06  // Publish the controls the app already shows.
07  const controls: LiveActionStore = {
08    subscribe: (onChange) => legacyStore.on('change', onChange),
09    actions: () =>
10      legacyStore.visibleControls().map((c) => ({
11        node: c.pageId,
12        name: c.id,
13        does: c.label,
14        handler: c.perform,
15        enabled: !c.disabled,
16      })),
17  };
18  
19  // The router table and the journey list ARE the graph.
20  export const graph = buildNavigationGraph('poc-app', {
21    sources: [fromRoutes(ROUTES), fromJourneys(JOURNEYS), fromLiveStore(controls)],
22  });
23  
24  // One session, driving the app's real router.
25  export const session = graph.createSession({
26    node: 'home',
27    navigate: (href) => router.push(href),
28  });
MEASURED, NOT ESTIMATED
28

the same integration, rewritten and compile-checked

814 B

measured against this release: fromRoutes, minified and gzipped — what one graph source adds to a bundle

1 day

a version migration, reported by a production integration

read the docs →see it work ↗no other numbers are claimed