The parts that need a paragraph.
Everything described here is built and running. Where something is unfinished, this page says so in the present tense rather than the future one.
Your agent has its own copy of these facts, written for a machine, at /agent.md. This page is the one for you.
01 · the declaration
One word decides what silence costs.
You pick one of four when you open an ask. It sets what your agent does while it waits and what happens if nobody answers, and the engine derives everything else from it — so there is no way to build an ask whose behaviour and whose description disagree.
courtesy
While it waits nothing is held. Your agent keeps going.
At the deadline there is nothing to resolve.
Kay is continuing either way — your thoughts are welcome whenever.
awaited
While it waits the work is held until the deadline.
At the deadline your agent proceeds without the answer.
Kay is holding this until Fri 5:00pm EDT, then continuing without it.
required
While it waits the work is held until the deadline.
At the deadline the work is dropped.
Kay can't continue without this. Unanswered by Fri 5:00pm EDT, it gets dropped.
blocking
While it waits the work is stopped, with no horizon.
At the deadline — there is no deadline. It never expires.
Kay's work is stopped until you answer. No deadline — it waits for you.
The sentence in each card is not our paraphrase
One function writes it, and it is rendered word for word in three places: the email the reviewer gets, the row in their inbox, and your agent's report back to you. If your agent restates it in its own words, you and the reviewer are working from two different promises. Ask it to quote.
The deadline is printed in your timezone, named out loud — "Fri 5:00pm EDT". We do not know the reviewer's timezone and refuse to guess it from their IP address, so a labelled time in your zone beats a confident wrong one in theirs.
Timing is not authority
A designer's taste note and a lawyer's veto have the same blocking shape and are completely different asks, and reviewers behave differently depending on which they think they hold. So when you ask for a ruling, the reviewer is told which it is: "Your ruling decides it" on a gate, "Your read informs it — Kay decides" otherwise.
Why an expired ask is unopposed or abandoned, and never approved
The field that resolves silence has exactly three values: proceed, abandon,
keep waiting. There is no approve. This is not a setting we
discourage — there is no column that could hold it, which is cheaper than
policing a rule and impossible to get wrong later.
So a deadline that passes with nobody answering closes the ask as unopposed (nobody objected) or abandoned (this ask declared the work gets dropped, and it was). Neither names a person who acted, because nobody acted. Only a ruling of ready means a human approved something. If your agent ever reports an expired ask as a yes, it invented that, and the payload it read says otherwise in the same words used here.
Two more fields worth understanding
scope is the list of paths your agent said it would leave
alone while the ask is open. On its own that is a promise. In Claude Code
the guard turns it into a refusal.
capsule is one sentence, at most 200 characters, saying what a
Ready will cause — "if you say Ready, this deploys to production
within the hour". It is shown to the reviewer above the buttons they press
and recorded with their answer, so "what was this person told it would do?"
has an answer later. The engine never writes one and never derives one from
the timing; if your agent offers to compose it, tell it what actually
happens instead. A consequence nobody stated is a consequence nobody is
accountable for. Over-length is refused rather than trimmed — half a
sentence about consequences is worse than none.
02 · two front doors
MCP or the skills.
Same API, same token, same account, same trail. This is not a fork in the road — installing both is fine, and if you move between agents it is what you want. The question is only what your client can do.
The skills
Ten Markdown files your agent reads, installed into whichever agent you already use. One install reaches Claude Code, Codex, Gemini CLI, Cursor, OpenCode, GitHub Copilot, Goose, Windsurf, Zed, Amp and the rest of the Agent-Skills ecosystem.
They run on your machine, which buys three things an HTTP server on the other side of the internet cannot have:
- Your files.
/hgd-share ./prototype.htmlis a path. Your agent reads it and uploads what it found. - Waiting. A gated ask can hold a long-poll open for 45 seconds at a time and park until the ruling lands, instead of polling.
- A local ledger.
.humangated/in your repo records each open ask, its declaration and its scope. That file is what the guard reads, and it is why a different session tomorrow — or on another machine — picks the parked work back up.
In Claude Code the plugin also installs three hooks: the PreToolUse guard, a SessionStart digest that tells you what came back while you were away, and a Stop re-check. The hooks are registered by the Claude Code plugin manifest, so the plugin install is the one that carries them.
npx skills@latest add Brightwing-Systems-LLC/humangated-skills -g --skill '*'claude plugin install humangated@humangatedThe skills are MIT and public. Ten Markdown files you can read before you install them, in the skills repo.
MCP
One URL and one header. Nothing lands on disk, and it reaches clients that will never support skills at all.
claude mcp add --transport http humangated https://humangated.ai/mcp --header "Authorization: Bearer hgd_…"{
"mcpServers": {
"humangated": {
"type": "http",
"url": "https://humangated.ai/mcp",
"headers": { "Authorization": "Bearer hgd_…" }
}
}
}
Seven tools — hgd_share, hgd_ask,
hgd_pull, hgd_status, hgd_trail,
hgd_list, hgd_delete — against the same owner API
the skills call. Get the token first: /hgd-login if you have
the skills installed, or sign in at
/token.
What MCP does not have
- No
wait. Clients impose their own tool timeouts, and a tool that blocks for 45 seconds reads as a hang. So a gated ask over MCP means pollinghgd_statusand leaving a resume note, not parking on a long-poll. - No filesystem.
hgd_sharetakes the content itself, not a path — your agent reads the file and passes what is in it. - No guard. The guard is a shell script on your disk that Claude Code runs before a tool call. A server at the far end of an HTTP request cannot install one.
- No login, and no local config. There is no
hgd_logintool; the token has to come from somewhere else first. - No override.
/hgd-unblockedits a local ledger MCP cannot see. - Bearer tokens only. Authentication is the same static
hgd_token in anAuthorizationheader, with aWWW-Authenticateheader pointing at where to get one. Any client that lets you set a custom header connects; one that requires the OAuth 2.1 discovery dance does not, because there is no protected-resource metadata document to discover.
The tools carry the hgd_ prefix on purpose. Some
clients present tool names unprefixed, and a bare delete from
an unidentified server is an ambiguous thing to put in front of a model.
Which one to use
- Your agent runs on your machine and can install files — the skills. In Claude Code, the plugin, because that is where the guard, the long-poll and the ledger all land.
- Your client speaks MCP and nothing else, or you would rather not have files in the repo, or the agent is not on your machine — MCP.
- You move between both — both. It is one account and one trail either way.
03 · the guard
The one thing that actually refuses.
Skill text is a request a model can decline. A hook is shell: it runs outside the model, before the tool call, and its verdict is not up for negotiation. This works in Claude Code only. Everywhere else the same declaration degrades to advice, and saying so is the point of having it.
The Claude Code plugin registers a PreToolUse hook on
Write, Edit, NotebookEdit and
Bash. It reads .humangated/BLOCKED at your repo
root, which /hgd-ask writes whenever an ask blocks anything.
What it refuses, by declaration
courtesy— never reaches the ledger. There is nothing to enforce.awaited— you may edit inside the scope. You may not land it:git commit,git push,git merge,git tag,npm publish,yarn publish,terraform apply,kubectl apply,docker push,make deploy,gh pr mergeandgh releaseare held.requiredandblocking— you may not touch the scope at all.
The part that took longest to get right:
git push origin main names no path and ships every gated file
that happens to be committed, so matching the command text would miss the
commonest way work escapes a gate. The guard asks git what the command would
actually put into the world — staged files for a commit, unpushed commits
for a push — and if git cannot answer, it holds. Being wrong about an
irreversible outward-facing action is the expensive kind of wrong.
Three things it deliberately will not do
- It never fails closed. No
jq, no ledger, a line it cannot parse, a network hiccup — it permits and says nothing. A guard that wrongly blocks gets uninstalled within the hour, and then it protects nothing at all. - It never blocks on the network. There is one liveness check, capped at two seconds, and its failure is treated exactly like its success. A hook that hangs is a hook people disable.
- It is overridable, on the record.
/hgd-unblock <uuid>takes your reason in your own words, writes it into.humangated/asks/<uuid>.md, and removes the line. It does not close the ask and it does not record a ruling — an overridden gate has no disposition, and nobody approved anything. An un-overridable guard gets uninstalled for the same reason a wrong one does.
A deadline that has passed releases the guard locally whatever the file still says: the engine has either acted already or is about to.
Everywhere else, it is advice. PreToolUse is a Claude
Code interface and no other harness has one. In Codex, Cursor, Gemini CLI
and the rest, the same declaration arrives as text a model reads — and a
model can decide otherwise. That is why the ask you sent still carries a
deadline, a declared sentence and a recorded outcome: the record does not
depend on the harness, only the refusal does.
04 · assurance
Three rungs, and none of them is identity.
This is the part of the product most likely to be read as more than it is, so here is exactly what each rung means and exactly where it stops.
Someone typed this address on a page they could already reach. Nothing was sent to it and nothing came back. On an open prototype this is the ordinary state, which is why it is the bottom rung.
Someone opened a link the person asking handed to that address. It shows the link reached them.
Someone opened a link we emailed to that address. An email round-trip, and nothing more.
Read those three again and notice what none of them says. Each one describes a mailbox and a click. A shared inbox, a forwarded link, a colleague on somebody else's laptop — all of them clear these rungs without anything noticing, because there is nothing here that could notice. We label how well an address was established, and we stop there. None of the three is proof of identity, we do not describe it as one anywhere in the product, and if you find a sentence that does, it is a bug worth reporting.
A floor, not a claim
An ask declares the floor it will accept an answer at:
any, vouched, or verified. A floor of
asserted is not spellable — "you must have typed something" is
not a requirement, it is a description of doing nothing.
A gate floors at verified by default, so the ruling comes from
the human's own click rather than from anything acting for them. Pass
any to opt out; answering a gate off a link you hand-delivered
is a legitimate thing to want, and the engine should not pretend
otherwise.
Raising the floor still introduces no account. The only thing an unmet floor ever does is email a fresh link to that address and explain itself: "This ask only takes an answer from someone who opened a link emailed to mike@partner.co. We have just sent a fresh one to that address — open it and answer from there." One tap, and still nothing to sign up for.
A ratchet on the person, a snapshot on the answer
A reviewer only ever moves up. What a response records is the rung it was written on, frozen at that moment. Somebody verifying next month does not strengthen the note they left today, and no later verification quietly upgrades an old record.
When several answers arrive in one burst, the rung we show for the burst is
the lowest one in it. A reviewer can comment while vouched and
rule after verifying; summarising that as verified would
describe the best of what happened rather than the floor of it.
No checksum raises the ceiling
The ceiling is set by the weakest path the person took to get there, and hashing their words does not move it. The checksum described in the next section is tamper-evidence about characters; it says nothing about who typed them. The two travel beside each other and neither implies the other.
Wherever a rung is printed, the sentence explaining it is printed with it. A bare badge with no gloss is how a reader invents the stronger claim we refused to make.
05 · provenance
The copy that counts is the one we cannot reach.
Every response gets a checksum of exactly the words in it. A checksum we compute and store proves nothing to anyone whose doubt is about us — so the anchor is the receipt email sitting in the reviewer's own mailbox, and the checksum is what makes that email checkable.
The checksum
hgr3: followed by 64 hex characters — a SHA-256 over a
canonical payload for one response. What goes into it: the response id, the
ask, the artifact, the version number, the reviewer as a keyed reference
rather than an address, the kind, the body verbatim, any suggested
replacement, the ruling, the option they picked, the text they anchored to,
the timestamp to whole seconds, the consequence capsule they were shown, and
the assurance rung the writing address stood on.
What is deliberately left out, and why:
- The reviewer's plaintext address. A receipt should not double as a lookup table for who reviews what.
- Anything derived — counts, labels, rendered HTML. A change to how we display something must never invalidate a record of what a human said.
- Our own status fields. The ask moves through queued, pulled and closed long after the person is done, and none of that changes what they wrote.
Text is normalised before hashing, because the same visible "é" has one encoding on macOS and another on Windows. Identical on screen, different bytes, different checksum — and a receipt that fails for a reason nobody can see is worse than no receipt.
The version is inside the value
hgr3: is not decoration. Canonicalisation will change — a field
gets added, a normalisation is tightened — and verification always uses the
scheme the record itself declares rather than the current one. Old schemes
stay implemented forever. Without that, every receipt issued a year ago would
silently stop verifying, and there would be no way to tell an old honest
record from an altered one.
The receipt email
When a reviewer answers, we email them their own copy: their words, the checksum of those words, and the sentence explaining the rung it was recorded at. That email is signed by our domain, timestamped by their mail provider, and sitting in a mailbox we cannot reach or alter. It is the part of this that is outside our control, which is the only part that does any work in an argument.
It also tells them, in the same email, what the checksum does not do:
What it does NOT do is prove who wrote it. Beside each entry we record how your address was established at the moment you wrote it. That is a mailbox, not a person, and we will never describe it as more than it is.
So the claim is small and it holds: quote them later and the same words produce the same checksum, and different words do not.
Why we call it provenance
We attest to what happened on our wire, and never to what it meant. That is provenance, not a certified audit, and the distinction is not modesty. An agency settling a scope dispute wants "you picked B on 3 July, here is the unaltered record", and that is exactly what this hands over. Claim more and the first person to lean on it in a real argument finds out — and everything else we ever recorded is worth less afterwards.
06 · typed asks
Ask for a value, not a paragraph.
The kind is the shape of the answer; the
declaration is the timing. They compose freely — a choice can be
courtesy or blocking like anything else.
choice — two to four options
Comes back as {"choice":"b","because":"…"}, in about fifteen
seconds. On a gate, the pick is the ruling.
When it beats prose: when you already have the alternatives. Your agent generates them constantly and the expensive part was never writing them, it was deciding between them. A pick is a value your agent branches on. A paragraph is something it has to interpret, and interpreting is where it guesses.
One option is a rhetorical question and both extremes are refused. Two is the smallest set that is a choice at all. Past four the reviewer is doing your agent's narrowing for it, and on a phone the options stop being a set you compare and become a list you scroll.
On a phone they stack full width, each one fully legible, with a sticky bar of jump chips at the bottom where a thumb already is — one tap moves between options at the same scroll position, which is what "side by side" actually buys you. Long bodies are clamped to a comparable height so one option cannot bury the other. On a wide screen they become real columns and the chips disappear, having nothing left to do.
form — one to five questions
Each question is text, scale (1–5),
bool, or pick (two to four choices).
When it beats prose: when you were unsure about several specific things and can name them. Your agent writes the form because it is the one that knows what it was unsure about.
Five is the cap, and a sixth question is refused with its reason attached: if you need more than five you are researching rather than asking — narrow it down or send two asks. The cap exists because the agent writes the form, and an agent with an unbounded question budget will spend it. Past five the reviewer is doing research for you, and "about four minutes" stops being a favour anyone does twice.
- Every question is optional, and a skipped one is recorded as absent rather than blank. Which they skipped is itself data, and a form that refuses to submit over question three is a form that gets abandoned instead.
- There is always a free-form field your agent did not write. It is where the thing it failed to ask about comes back.
- The time estimate is summed from what each question costs — a tap is a tap, typing is the expensive one — and shown to the reviewer before they start. An invented minute on a fifteen-second ask is the same failure as a deadline nobody enforces: the first person who notices discounts every later estimate.
- Only the two real booleans count. Anything unexpected is dropped
rather than quietly recorded as "No", and a
pickanswered as free text is dropped for the same reason. Recording a position the reviewer never took is the worst available way to be wrong.
review — the default
Their words on the thing, about a minute. Use it when you do not know what you do not know. Nothing rewrites, scores or summarises what comes back over the top of it — a machine gloss can sit beside a person's words, labelled, and never in place of them.
Your ask gets checked; their answer never does
Every ask is checked for answerability before it is sent, whatever its kind. A rejection names exactly what is wrong: an objective that asks nothing, one that names nothing, three questions in the slot for one, a leftover {{placeholder}}, options nobody could tell apart, or words that contradict the declaration you chose. Your agent can fix that in a second, and the human on the other end cannot fix it at all. The check scores your ask, it is never shown to the reviewer, and nothing anywhere ever scores a reviewer's answer.
07 · livegate
Dormant until one named person arrives.
Nothing is published, so you cannot install this today. The npm
package and the version-pinned CDN build both exist in the overlay repo
and neither has been released, so a stock deployment has no script URL and
the template tag renders nothing at all. A <script src>
pointing at a 404 on your production page is worse than no feature. The
rest of this section is what it does, and it is accurate — you can
register an origin at
/dashboard/sites/
and read the exact snippet with your key filled in, which is enough to put
it in front of a security review now.
What works today without it
You can already point an ask at a URL your own app serves. The reviewer gets a plain link, opens the page in their own browser, and answers in their inbox. We never load, embed, fetch or screenshot that page, and nothing is installed anywhere. That is the loop without the overlay. LiveGate is the overlay on top of it — annotating the page itself instead of describing it from memory.
Dormant is the whole design
On an ordinary page view the script reads one string, finds no grant in the
URL and no live session in sessionStorage, and returns. No
network request, no DOM node, no event listener, no timer. Your visitors,
your customers and your own team see the page you shipped.
It wakes only when a person we named arrives holding a grant: single-use, short-lived, minted for one reviewer on one ask about one page. The first thing it does — before it has read its own configuration — is take that token out of the address bar, so it cannot reach your access logs, your analytics, a referrer header or a screenshot. The session that replaces it expires on our clock and cannot renew itself; a new one needs a new link from their inbox.
What it touches on your page, and what it does not
- It adds a floating button and, when the reviewer uses it, an overlay for pinning a comment on an element or answering a typed ask. It traps focus while the composer is open and restores it after, and it tears itself down when the session expires.
- It grants annotation, never access. If the page is behind a login, you provide the way in. This is not an authentication bypass, and that is a claim your security review is welcome to test.
- It redacts by default. Inputs are masked and anything you mark
data-hg-redactis blanked. The redaction list and the screenshot setting are server-side policy per registered site, not a flag your page can set for itself — otherwise "screenshots are off unless the site turns them on" would describe the client's good manners rather than a rule. - There are no screenshots in v1. A rasteriser is a dependency and a much larger exfiltration surface.
- It cannot read your cookies. Requests go out with credentials omitted and we never allow credentialed cross-origin responses.
The two facts people get wrong
The CSP entry names this application's own origin —
https://humangated.ai — and not an api.
subdomain, because there is no such host. Two paths are the only ones on our
server that answer a cross-origin request at all, and they answer only for an
origin you registered, echoed from your row rather than reflected from the
request.
The site key in your page source is public and authorises nothing.
It says "this page claims to be that origin", and the server answers "then
your Origin header had better be the one on file". The
credential is the handoff, minted server-side for one named reviewer.
Known limits
Shadow DOM and cross-origin iframes anchor to the host element rather than refusing with an explanation. Removing the grant from the address bar is untested against a single-page-app router that re-renders on it. A gated ask against a URL is still ruled on in the reviewer's inbox, not in the overlay.
The overlay is MIT, has no runtime dependencies, and is about 8 KB gzipped. Its integrity hash is reproducible from a clone, so you never have to take our listing's word for it.
08 · the trail
Filter on any part of the atom.
One ask is one artifact, one version, one question, one person. That is the unit the record is kept in, so every element of it is something you can filter on — and they AND together.
The artifact · the version number · the reviewer · words in the objective ·
your own external reference · the ask id · the declaration
(courtesy, awaited, required,
blocking) · the kind · the status · where the ask came from ·
the ruling, including none · what you did with it afterwards
(applied, declined, superseded) · how it expired · the assurance rung
recorded · a date range.
So "every required ask on the refund prompt that Mike let
expire" is one query rather than three exports and a spreadsheet. A filter
value the page cannot parse narrows nothing instead of erroring, which is
why the report is safe to bookmark.
Three shapes, one source
- The asks CSV — one row per ask with every element of it: the declared sentence, the deadline, the scope, the capsule, the expiry outcome, the assurance rungs recorded, and the checksums.
- The events CSV — the timeline. Ask sent, email delivered or bounced or marked as spam, inbox opened, answered, option picked, form answered, forwarded to someone else, hand-delivered link redeemed, pulled by your agent, what you recorded doing with it, deadline passed with no answer, reminder sent, receipt sent, closed.
- A printed record — the record page carries a print stylesheet, so your own browser makes the file. There is no rasteriser on our side, no fonts to license and nothing to keep alive. The trade is that you cannot email a PDF from here.
Only actions that mean something to somebody outside the building are exported. The full event vocabulary is a debugging tool, and a record padded with token mints is a record nobody reads to the end.
Reviewer IP addresses are deliberately absent
This was decided once rather than per surface: the address stays in the internal event log and reaches no owner-facing or reviewer-facing surface, ever. An export is an owner-facing surface, exports get forwarded, and a person who never signed up for anything did not agree to that. The export drops those fields by name rather than by convention, so nobody adding a field later has to remember.
There is no geolocation either, at any resolution. City-level at best, wrong through VPNs and carriers, and personal data collected from someone who has no account. If you want a reviewer's local time, they have to tell you.
Every export carries its own limits in its first four lines, so the document arrives wherever it is going with them attached: a record of what happened on our wire, provenance rather than a certified audit, and reviewer IP addresses not included. The assurance column records the rung at the time of writing, never the reviewer's rung today.
09 · what is metered
One number is counted, and you choose it.
The count is asks you open — not emails, not reviewers, not artifacts. It moves only when you decide to ask somebody something.
One ask counts once, whatever happens to it. The reminder at T-24h, the receipt the reviewer gets, the expiry notice and the nudge when an answer is sitting unpulled are all free. Those are ours to send, not yours to trigger, and a quota with invisible line items is the kind people are right to be angry about.
Free is fifty a month. Calendar month, UTC, resetting at midnight on the 1st. Paid plans are not counted.
Nothing already open is ever affected. The count gates opening a new ask and touches nothing else. An ask you have already sent keeps its deadline, still reminds, still expires exactly as it declared, still accepts a ruling and can still be pulled — through a month roll, through a downgrade, through a lapsed card. An agent blocked on a gate cannot be stranded by a billing event, which is the one failure this product could not live with.
When you are over, the answer is an HTTP 402 saying the number, the reset date and what removes the cap. Your agent gets the same sentence, and it is an error rather than a result — a refused ask that read as "asked, waiting" would be a way to walk through a gate.
There is also a daily ceiling on every plan, well above anything a person does, which exists so a looping agent finds out in hours rather than at month end. If you meet it legitimately, say so and it moves.
Two things that are not metered and will not be: how many reviewers you ask, and how many people ever answer. Everything a reviewer touches is free, always.
10 · teams
Set the rule once, not per ask.
An organisation is several people and one record. Kay's agent asks Mike, Dana's agent asks Mike, and both are on the same trail.
Rows still say who wrote them. Nothing moves to a company column — the ask still records the person who opened it, because saying who did what is the trail's entire job. What widens is who may read it.
The plan lives on the organisation. A seat inherits its tier, so two colleagues cannot disagree about what they are paying for. One person is in one organisation, which keeps "whose trail is this ask on" a question with one answer.
Standing sign-off rules. Name a path glob and a reviewer once — src/billing/** to Mike, as required — and every ask any seat opens over those paths is raised to meet it. A rule can only strengthen: an agent that asked for blocking under a required rule keeps its harder gate, because being more careful than the company requires is not a thing to correct.
When a rule changes an ask it says so, in the organisation's own words, including the reason you wrote on it. A rule that silently rewrote an ask would read as a bug the first time somebody met it.
The rules are also published at GET /api/requests/rules, which is how the PreToolUse guard refuses an edit before anybody has decided to ask. That half needs a skills release; the engine-side raise is live now.
Two things that stay per person on purpose: the daily ceiling, because a shared one would let one colleague's runaway loop lock out the other ninety-nine, and Slack, because a webhook points at one person's own channel.
and that is all of it
Nothing on this page is a plan. If something here turns out not to be true of the running product, that is a bug and it is worth telling us about: support@humangated.ai.
The agent-shaped version of the same facts lives at /agent.md. The skills your agent runs are public and MIT, in the skills repo. The service itself is closed source.