Skip to main content

awnboard

A front gate you can put in front of anything — and hand someone the key to.

pip install awnboard
from awnboard import Gate, Invited, HmacSigner, GrantLedger, invite

signer = HmacSigner(SECRET)
ledger = GrantLedger("./grants.json")

token = invite(signer, gate="beta", to="sam@example.com", uses=1)   # email this

gate = Gate("beta", target="https://internal.example/app",
            requirements=[Invited("beta", signer, ledger)])

a = gate.admit({"invitation": token, "as": "sam@example.com"})
a.admitted            # True
a.message()           # what to show the visitor
a.reasons()           # what to show the operator

The problem, stated exactly

Letting one specific person reach one specific thing is still either a whole identity deployment or a link anyone who sees it can use. So everybody ships the link — and a link has a property nobody wants: the link IS the credential.

Forwarding it transfers it. A screenshot is a copy. It does not know who it was for, so nothing can tell you who used it. And revoking it means rotating something everyone else is still using, which is why nobody does, which is why the invitation you regret is still live.

An invitation here names its recipient:

field drop it and…
gate it opens whichever door it is shown to
to it is a share link again — forwarding works
uses one solve opens the door forever
exp the damage is unbounded in time
id you can only revoke everyone, by rotating the key
gate.admit({"invitation": token, "as": "mallory@example.com"})   # refused

The signature is perfect. It is still refused, and the refusal does not tell Mallory who it was for — a gate must not become a directory of who was invited.

The ladder

A gate holds requirements, and every one must be met. There is deliberately no "any of these" mode: an OR ladder grows a weak rung nobody removes, and before long every visitor arrives through it while the strong rungs sit there looking reassuring. Two gates in front of two targets is the honest way to say "these people, or those people".

Gate("beta", target=url, requirements=[
    Invited("beta", signer, ledger),                  # a grant addressed to you
    Passcode(digest, salt=salt),                      # something you were told
    KnownSubject(["u_42", "u_77"]),                   # an allowlist
    VerifiedHuman("app:mine", NEST_SECRET),           # is there a person there
])

Every rung is unmet by default. A requirement that cannot evaluate — no input, a checker that is not installed, a ledger it cannot read — returns unmet, never met. There is no path through this package where an error becomes admission, and a rung that raises is unmet rather than a 500 somebody later wraps in a try/except that admits.

VerifiedHuman delegates to awnest when it is installed, and is unmet — not skipped when it is not. A gate that quietly drops its strongest requirement because a dependency is missing is worse than one that refuses.

Refusals, for two audiences at once

a.message()   # "That invitation was sent to someone else. Ask for your own."
a.reasons()   # "invited: invitation is addressed to someone else"
a.as_record() # a flat audit record — no tokens, no secrets

The visitor gets remedies: actionable, and carrying nothing that helps anyone guess. The operator gets every rung's verdict. Both come off the same object, because two objects drift and the one that drifts is the one nobody reads.

Every unmet rung is shown, not just the first — telling someone the code was wrong, and then, after they fix it, that they also needed an invitation, is how a two-step gate becomes a support ticket.

Revocation is per grant

ledger.revoke("inv-3", reason="left the project")   # just that one
ledger.history("inv-3")                             # who walked through, and when

That last line is the question a share link cannot answer at all.

The ledger is a JSON file on purpose: a gate in front of one thing should not require anyone to run a service, because the moment it does, people put the gate somewhere else. The interface is four methods — reimplement it over Redis or Postgres when a deployment needs a real compare-and-set. Two simultaneous acceptances of a one-use invitation can both land against the file version; that is written down in the docstring rather than hidden, because the honest limit of a small thing is more useful than a big thing nobody deploys.

An unreadable ledger fails closed. Read as empty it would un-revoke everything and re-open every one-use invitation at once — the quietest possible catastrophe.

What it composes with

with what it adds
a human check is there a person there at all, at what strength
an identity system who specifically, once they are through
an authz system what they may do on the other side
an audit trail who walked through, kept where a missing record shows
a tunnel or a mesh a target with no public address, reachable through the gate
signing + content-addressed transfer hand someone a sealed grant instead of a link

None of it is a dependency. A gate with a passcode and an invitation works with nothing else installed — that is the point of a brick.

Command line

awnboard invite  --gate beta --to sam@example.com --uses 1
awnboard read    TOKEN --gate beta          verify without spending it
awnboard admit   --gate beta --invitation TOKEN --as sam@example.com
awnboard revoke  INVITATION_ID --reason "left the project"
awnboard history INVITATION_ID
awnboard hash    --code sunrise-42 --salt <salt>
awnboard --self-test

Exit codes: 0 admitted / ok · 1 refused or broke · 2 you asked wrongly.

The signing secret comes from --secret or AWNBOARD_SECRET and is never defaulted: a default key is a key everybody has, and here it is the key that issues invitations.

Licence

Apache-2.0.

The aw family

Standalone tools that share one idea: replace something you would otherwise have to trust with something you can check.

Each installs on its own, works offline, and needs no account.

instead of trusting you check
awdk a framework's idea of how your agents should run one loop you can read, pointed at a backend you already pay for
awskills that an agent knows your procedure the procedure written down, versioned, and loadable by any agent
awm that memory stayed in its lane tenant:user:project scopes, so a write cannot cross a boundary
awnode a vendor's cloud with every prompt a local gateway routing to backends you chose
awgraph that grep found everything an AST + tree-sitter call graph an agent can traverse
awgit that no one else is editing this file a lease, refused at commit time if you do not hold it
awseal that the artifact came from who you think an Ed25519 seal — the key that verifies is not the key that forges
awshare that the download is intact content-addressed bundles, verified on fetch
awnest that there is a person on the other end a verdict with evidence, where "we could not tell" is not "yes"
awnboard (you are here) a share link anyone who sees it can use an invitation addressed to one person, for one gate, revocable
awnix that the box is what you left it as an immutable image you built, with atomic rollback
awrecover that the restore worked a restore that fully lands or does not land at all
awkno that the docs site is up, or that you remember the family the whole ecosystem in your terminal, with no network at all
awrelay a SaaS in the middle of your agents findings, alerts and coordination over your own transport
awmail a mailbox somebody else can read mail your agents send and receive over your own server
awfind one vendor's idea of the web results from whichever providers you configured
awbrowse that the page said what you were told the render, the DOM and the requests it made
aitherkvcache a vendor's quantisation defaults sub-byte KV cache kernels you can benchmark yourself
AitherZero a pile of scripts nobody has numbered numbered, discoverable automation with declarative playbooks
AitherConnect what a page tells your browser to do a federated search and desktop bridge you host
awreason a confident paragraph the phases it went through, and every tool call it made to get there
awrecurse that everything you pasted in was actually read which slices it opened, and what it concluded from each
awprism the first explanation that fits the ranked alternatives, and the observation that separates them
awrepl what the agent believes the value is the value, printed from the live session
awresearch a summary of pages nobody opened every claim against the source it came from

awnix is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.

The Aitherium ecosystem

Every repository here is public. Each publishes an aither-manifest.json beside its page, so any surface can read every sibling's — the network is browsable from any node in it.

repo what it is pages
awdk Build AI agent fleets — 3 lines, any backend, local or cloud docs
awskills Portable agent skills — self-contained procedures an agent loads on demand docs
awm A portable, scoped agent memory docs
awnode A lightweight local gateway — bridges your apps to the AI backends you chose docs
awrun A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds docs
awgraph A semantic code graph for agents — AST + tree-sitter, call graphs docs
awgit Semantic version control on top of git — edit-ops and leases docs
awseal Sign an artifact so a stranger can verify it docs
awshare Publish an artifact and fetch it back verified docs
awnest Prove there is a human before you let them into the nest docs
awnboard (you are here) A front gate you can put in front of anything, and hand someone the key to docs
awnix A Linux you can hand to an agent — immutable base, capabilities included docs
awrecover Labelled snapshots with an all-or-nothing restore docs
awkno The man page for the Aither World — every brick, stack and law, offline docs
awrelay Portable agent messaging — findings, alerts, coordination docs
awmail Give an agent an email address — send, and actually receive docs
awfind A portable search client — query, results, ranking docs
awbrowse A portable browser client — navigate, console, network, DOM, screenshot docs
aitherkvcache Near-optimal KV cache quantization for LLM inference — sub-byte compression docs
AitherZero PowerShell 7+ automation framework — numbered, self-describing scripts docs
AitherConnect Browser extension — federated AI search, page context, and the Living OS overlay docs
awreason A portable reasoning client — sessions, phases, thoughts, and the chain that produced the answer docs
awrecurse Answer a question over a context far larger than the window — recursively, with the trace kept docs
awprism Turn a failure into ranked hypotheses — and say what would confirm each one docs
awrepl A REPL an agent can actually use — state that survives between turns docs
awresearch Ask a research question, get a cited report you can check docs

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

awnboard-0.1.0.tar.gz (35.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

awnboard-0.1.0-py3-none-any.whl (30.8 kB view details)

Uploaded Python 3

File details

Details for the file awnboard-0.1.0.tar.gz.

File metadata

  • Download URL: awnboard-0.1.0.tar.gz
  • Upload date:
  • Size: 35.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for awnboard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ed87cabd3377443fa1e4aca847b57342af3a9e5709da2e0870a9789426f6ac53
MD5 6bad70e3d006da53e437344c6f3636d1
BLAKE2b-256 0431eac59bf854a57321ebfca3056be90ff64ee9b11b5d706afb399530ebf02f

See more details on using hashes here.

File details

Details for the file awnboard-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: awnboard-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for awnboard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae40c213441b1e73c3fd46b1cc480fdeedc8f933437457ed039b4f8980ca96d4
MD5 21dbad1accbf3c31b6063c26d76c9af6
BLAKE2b-256 c705336b08d67f2bf2b008e57a57d66782ac8c89e45a380ac7a7faf247db8a51

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page