Inspiration

An agent loses its context, and it does not just lose information. It loses the prohibitions.

The approach you ruled out last week. The dependency you refused. The step it already finished. A conversation summary keeps the highlights and sacrifices exactly what constrains, because a constraint reads like a detail right up until someone breaks it.

So you explain it again. And the agent proposes, with confidence, the thing you eliminated together on Tuesday.

We wanted to know whether that was actually measurable, or just a feeling. It is measurable.

What it does

Keydler takes those constraints out of the conversation and puts them on a web page that an agent reads and writes through WebMCP: rules in force, work done with its evidence, approaches ruled out with the reason, and the next action.

The part that matters is not that the human approves things. It is that the human can change the rules while the agent is working, and the page guarantees the agent finds out.

Every agent write carries the version it read. A human write never does. So when you add a rule mid-flight, the agent's next write is refused:

STALE STATE
You are attempting to log work based on task state v5.
Current state is v6. No write took place.
Call what_changed with since_version: 5 to see only what moved.

It asks for the delta, reads the new rule, and adapts. Without that refusal it would overwrite the rule you had just set, and neither of you would know ; you would find out from the result.

Fourteen tools, four read and nine write, plus one that opens the log when none exists. Browser-local: no account, no server, nothing leaves the device.

The measurement

Without the log, an agent proposed the ruled-out approach again in 8 cases out of 8. With the log, in 0.

Eight tasks, two conditions, one binary question.

We got the design wrong first, and that failure taught us the product. Our initial tasks ruled out classic anti-patterns ; localStorage for tokens, OFFSET pagination. The control scored zero: a capable model avoids those unprompted, and rules them out by name without being asked. There was nothing to measure.

So we inverted it. What has to be ruled out is not the bad answer. It is the good answer, set aside for a local reason. Ban the Redis token bucket because operations refused Redis. Ban cursor pagination because a backfill broke id monotonicity. Those are the right answers everywhere except here, for reasons no model can deduce.

That is when the control started failing 8 out of 8, and it is the whole content model of the product.

What the number hides is worth more than the number: no agent dodged a keyword. They read the reason and kept the part still valid ; "what was rejected is the Redis backing, not the bucket algorithm."

And its limits, stated plainly: eight runs per condition, one model, correlated results. Exploratory, not statistical. No percentage is derived from it.

How we built it

TypeScript, one runtime dependency, IndexedDB, no framework. The domain is pure ; no DOM, no storage, no WebMCP ; so the guarantees are testable without a browser.

The core is a versioned write queue. The version comparison happens inside the IndexedDB transaction, so two tabs cannot silently overwrite each other. Each write carries a mutation_id and an argument fingerprint: the same id with the same arguments replays the original reply verbatim; the same id with different arguments is refused and audited, rather than acknowledged as work that never happened.

The interface is Material Design 3, implemented from the specification. The page keeps a default-src 'none' content policy.

Challenges

A fake cannot fail in a way it was not written to fail. Our 946 tests run against a ModelContext written from the spec IDL. Every time we opened a real browser, it told us something the fake could not: executeTool refuses a tool name and wants the object from getTools(); its arguments must be a JSON string; its answer comes back serialized. Four times.

The most useful bug was found by an agent, yesterday. In ChatGPT's built-in browser, GPT-5.6 Terra found the tools, then stopped and asked the human for permission ; because it believed creating a task would send the project details to keydler.com. The caution was right. The premise was wrong: nothing leaves the page.

We added that fact to the tool description. It still asks. And it should: passing arguments to code served by a site is giving that site data, and our claim that we do nothing with it is unverifiable from where the agent sits. An agent that took a website's word for "I don't send your data anywhere" would be a worse agent.

So we stopped writing copy to defeat a safety check. The result is better than what we designed: the agent pauses for consent on its own, with nothing staged.

Two defects found by using our own product. requestAnimationFrame never fires in a hidden tab ; and a hidden tab is exactly this product's case, since the human is in their agent's conversation while writes land here. The attention title, the one signal that reaches someone looking elsewhere, never updated. And in production, one no-transform header leaked from the document rule onto the hashed assets, shipping 186 kB of uncompressed JavaScript and silently defeating a year-long cache.

What we learned

A tool description cannot make an agent look at a page. Asked a bare architecture question with the tools registered and visible, both Claude and Terra answered from their own knowledge and never touched the page. Named the page ; "Continue this task on this page" ; and Terra read it immediately. Discovery is a separate problem from description, and we can only measure it, not promise it.

Write down the runs that went badly. Our verification log records a voided measurement run, a contaminated trial, and every check we could not observe. It is longer for it, and it is the only reason the 8/8 is worth anything.

What's next

Rerun the measurement with more models and publish the exhibits. Test whether an agent that has used the log once reaches for it unprompted the second time. And find out what happens on Chromium 153, where tools can finally be withdrawn safely.

Built With

  • team
Share this project:

Updates

Submission history